# 품목관리 마이그레이션 가이드 (Next.js 15) > **작성일**: 2025-11-13 (Updated) > **프론트엔드**: Next.js 15 App Router + React 19 > **백엔드**: PHP Laravel > **상태 관리**: Zustand > **소스**: React 프로젝트 → Next.js 15 마이그레이션 --- ## 📑 목차 1. [프로젝트 개요](#1-프로젝트-개요) 2. [하이브리드 아키텍처](#2-하이브리드-아키텍처) 3. [데이터 구조](#3-데이터-구조) 4. [Next.js 15 구조](#4-nextjs-15-구조) 5. [API 연동 전략](#5-api-연동-전략) 6. [마이그레이션 계획](#6-마이그레이션-계획) 7. [Zustand 상태 관리](#7-zustand-상태-관리) 8. [Server/Client Components](#8-serverclient-components) 9. [주의사항](#9-주의사항) --- ## 1. 프로젝트 개요 ### 1.1 목표 **1차 목표**: 물리적 페이지 구축 및 Laravel API 연동 **2차 목표**: 템플릿 기반 동적 페이지 생성 시스템 (선택적 확장) ### 1.2 핵심 요구사항 1. ✅ **품목 유형 관리**: 제품/부품/원자재/부자재/소모품 (FG/PT/SM/RM/CS) 2. ✅ **계층 구조**: 제품이 최상위, BOM을 통해 하위 품목 연결 3. ✅ **유형별 고유 필드**: 각 품목 유형마다 전용 입력 항목 4. ✅ **Laravel API 연동**: RESTful API 호출 5. ✅ **Next.js 15 최적화**: Server Components, App Router 6. 🎯 **하이브리드 전략**: 물리적 페이지 (80%) + 동적 템플릿 (20%) ### 1.3 프로젝트 환경 #### 소스 프로젝트 - **경로**: `/Users/byeongcheolryu/codebridgex/sam_project/sma-react-v2.0` - **스택**: React 18 + Vite - **메인 파일**: - `src/components/ItemManagement.tsx` (7,919줄) - `src/components/contexts/DataContext.tsx` (6,697줄) - `src/components/ItemMasterDataManagement.tsx` (1,413줄) #### 타겟 프로젝트 - **경로**: 현재 프로젝트 (sam-react-prod) - **스택**: Next.js 15 + React 19 + Zustand - **특징**: ```json { "next": "15.5.6", "react": "19.2.0", "tailwindcss": "4", "zustand": "5.0.8", "next-intl": "4.4.0", "react-hook-form": "7.66.0", "zod": "4.1.12" } ``` --- ## 2. 하이브리드 아키텍처 ### 2.1 전략 개요 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 품목관리 하이브리드 시스템 │ └─────────────────────────────────────────────────────────────────┘ │ ┌────────────┴────────────┐ │ │ ┌──────────▼──────────┐ ┌─────────▼──────────┐ │ 🏢 물리적 페이지 │ │ 🎨 동적 템플릿 │ │ (Next.js 페이지) │ │ (DB 기반 생성) │ │ │ │ │ │ ✅ 80% 사용 케이스 │ │ ✅ 20% 특수 케이스 │ │ ✅ 타입 안정성 │ │ ✅ 고객사 커스터마이징│ │ ✅ 빌드 타임 최적화 │ │ ✅ 런타임 유연성 │ │ ✅ Server Components│ │ ✅ 코드 수정 불필요 │ └──────────┬──────────┘ └─────────┬──────────┘ │ │ └────────────┬────────────┘ │ ┌──────────▼──────────┐ │ Zustand Store │ │ (전역 상태 관리) │ │ │ │ - itemStore │ │ - templateStore │ └──────────┬──────────┘ │ ┌──────────▼──────────┐ │ Laravel API │ │ │ │ - REST API │ │ - PostgreSQL/MySQL │ │ - File Storage │ └─────────────────────┘ ``` ### 2.2 왜 하이브리드인가? #### 물리적 페이지 (우선 구축) **장점**: - ✅ **성능**: Server Components로 빌드 타임 최적화 - ✅ **안정성**: TypeScript 타입 체크, 컴파일 타임 검증 - ✅ **SEO**: 서버 렌더링 자동 지원 - ✅ **개발 속도**: 명확한 구조, 빠른 개발 **사용 케이스** (80%): - 제품(FG), 부품(PT), 원자재(RM), 부자재(SM), 소모품(CS) 등록 - 표준 BOM 관리 - 일반 품목 조회/수정 #### 동적 템플릿 (선택적 확장) **장점**: - ✅ **유연성**: 코드 수정 없이 DB로 페이지 생성 - ✅ **커스터마이징**: 고객사별 특수 필드 추가 - ✅ **실험**: 시범 운영, A/B 테스트 **사용 케이스** (20%): - 고객사 전용 품목 페이지 - 프로젝트별 특수 품목 - 시범 운영 페이지 ### 2.3 데이터 흐름 #### 물리적 페이지 흐름 ``` 사용자 요청 ↓ Server Component (RSC) ↓ Laravel API 직접 호출 (서버) ↓ 데이터 fetching ↓ Client Component로 전달 ↓ 사용자 인터랙션 (Zustand) ↓ API 변경 요청 ↓ Revalidation ``` #### 동적 템플릿 흐름 ``` 사용자 요청 ↓ Template 조회 (DB) ↓ DynamicForm 렌더링 ↓ 조건부 필드 표시 ↓ 사용자 입력 ↓ Laravel API 전송 ``` --- ## 3. 데이터 구조 ### 3.1 ItemMaster (품목 마스터) ```typescript interface ItemMaster { // === 공통 필드 (모든 품목 유형) === id: string; itemCode: string; // 품목 코드 (예: "KD-FG-001") itemName: string; // 품목명 itemType: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; unit: string; // 단위 (EA, SET, KG, M 등) specification?: string; // 규격 isActive?: boolean; // 활성/비활성 // === 분류 === category1?: string; // 대분류 category2?: string; // 중분류 category3?: string; // 소분류 // === 가격 정보 === purchasePrice?: number; // 구매 단가 salesPrice?: number; // 판매 단가 marginRate?: number; // 마진율 processingCost?: number; // 가공비 laborCost?: number; // 노무비 installCost?: number; // 설치비 // === BOM (자재명세서) === bom?: BOMLine[]; // 하위 품목 구성 bomCategories?: string[]; // BOM 카테고리 // === 제품(FG) 전용 필드 === productCategory?: 'SCREEN' | 'STEEL'; // 제품 카테고리 lotAbbreviation?: string; // 로트 약자 (예: "KD") note?: string; // 비고 // === 부품(PT) 전용 필드 === partType?: 'ASSEMBLY' | 'BENDING' | 'PURCHASED'; partUsage?: 'GUIDE_RAIL' | 'BOTTOM_FINISH' | 'CASE' | 'DOOR' | 'BRACKET' | 'GENERAL'; // 조립 부품 installationType?: string; // 설치 유형 (벽면형/측면형) assemblyType?: string; // 종류 (M/T/C/D/S/U) sideSpecWidth?: string; // 측면 규격 가로 (mm) sideSpecHeight?: string; // 측면 규격 세로 (mm) assemblyLength?: string; // 길이 (2438/3000/3500/4000/4300) // 가이드레일 guideRailModelType?: string; // 가이드레일 모델 유형 guideRailModel?: string; // 가이드레일 모델 // 절곡품 bendingDiagram?: string; // 전개도 이미지 URL bendingDetails?: BendingDetail[]; // 전개도 상세 데이터 material?: string; // 재질 (EGI 1.55T, SUS 1.2T) length?: string; // 길이/목함 (mm) bendingLength?: string; // 절곡품 길이 규격 // === 인정 정보 (제품/부품) === certificationNumber?: string; // 인정번호 certificationStartDate?: string; // 인정 유효기간 시작일 certificationEndDate?: string; // 인정 유효기간 종료일 specificationFile?: string; // 시방서 파일 URL specificationFileName?: string; // 시방서 파일명 certificationFile?: string; // 인정서 파일 URL certificationFileName?: string; // 인정서 파일명 // === 메타데이터 === safetyStock?: number; // 안전재고 leadTime?: number; // 리드타임 isVariableSize?: boolean; // 가변 크기 여부 revisions?: ItemRevision[]; // 수정 이력 createdAt?: string; updatedAt?: string; } ``` ### 3.2 BOMLine (자재명세서) ```typescript interface BOMLine { id: string; childItemCode: string; // 하위 품목 코드 childItemName: string; // 하위 품목명 quantity: number; // 기준 수량 unit: string; // 단위 unitPrice?: number; // 단가 quantityFormula?: string; // 수량 계산식 (예: "W * 2", "H + 100") note?: string; // 비고 // 절곡품 관련 isBending?: boolean; bendingDiagram?: string; // 전개도 이미지 URL bendingDetails?: BendingDetail[]; // 전개도 상세 데이터 } ``` ### 3.3 BendingDetail (절곡품 전개도) ```typescript interface BendingDetail { id: string; no: number; // 번호 input: number; // 입력값 elongation: number; // 연신율 (기본값 -1) calculated: number; // 연신율 계산 후 값 sum: number; // 합계 shaded: boolean; // 음영 여부 aAngle?: number; // A각 } ``` ### 3.4 동적 페이지 관련 (선택적) ```typescript // 템플릿 시스템용 (2차 목표) interface ItemPage { id: string; pageName: string; // 페이지명 itemType: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; sections: ItemSection[]; // 페이지 내 섹션들 isActive: boolean; // 사용 여부 absolutePath?: string; // 절대경로 createdAt: string; updatedAt?: string; } interface ItemSection { id: string; title: string; // 섹션 제목 description?: string; // 설명 category?: string[]; // 카테고리 조건 fields: ItemField[]; // 섹션에 포함된 항목들 type?: 'fields' | 'bom'; // 섹션 타입 order: number; // 섹션 순서 isCollapsible: boolean; // 접기/펼치기 가능 여부 isCollapsed: boolean; // 기본 접힘 상태 } interface ItemField { id: string; name: string; // 항목명 fieldKey: string; // 필드 키 property: ItemFieldProperty; // 속성 displayCondition?: FieldDisplayCondition; // 조건부 표시 } ``` --- ## 4. Next.js 15 구조 ### 4.1 디렉토리 구조 ``` src/ ├── app/ │ └── [locale]/ │ ├── (protected)/ │ │ ├── items/ # 🏢 물리적 페이지 (우선 구축) │ │ │ ├── page.tsx # 품목 목록 (Server Component) │ │ │ ├── create/ │ │ │ │ └── page.tsx # 품목 등록 │ │ │ └── [id]/ │ │ │ ├── page.tsx # 품목 상세 │ │ │ └── edit/ │ │ │ └── page.tsx # 품목 수정 │ │ │ │ │ ├── item-templates/ # 🎨 동적 페이지 (선택적) │ │ │ └── [pageId]/ │ │ │ └── page.tsx # 템플릿 기반 렌더링 │ │ │ │ │ └── item-master-data/ # 🛠️ 관리 도구 │ │ └── page.tsx # 템플릿 생성/편집 │ │ │ └── api/ # API Routes (선택적 프록시) │ └── items/ │ └── route.ts │ ├── components/ │ ├── items/ # 품목 관리 컴포넌트 │ │ ├── ItemForm.tsx # 'use client' │ │ ├── ItemList.tsx # Server Component 가능 │ │ ├── ItemListClient.tsx # 'use client' (상호작용) │ │ ├── BOMManager.tsx # 'use client' │ │ ├── BendingDiagramInput.tsx # 'use client' │ │ └── FileUpload.tsx # 'use client' │ │ │ ├── dynamic-forms/ # 동적 폼 (선택적) │ │ ├── DynamicForm.tsx # 'use client' │ │ ├── DynamicField.tsx │ │ └── ConditionalSection.tsx │ │ │ └── ui/ # shadcn/ui 컴포넌트 │ ├── button.tsx │ ├── input.tsx │ ├── select.tsx │ ├── form.tsx │ └── ... │ ├── stores/ │ ├── itemStore.ts # Zustand - 품목 상태 │ ├── templateStore.ts # Zustand - 템플릿 상태 │ └── types.ts # 공통 타입 정의 │ ├── lib/ │ ├── api/ │ │ ├── items.ts # 품목 API 클라이언트 │ │ ├── bom.ts # BOM API 클라이언트 │ │ └── templates.ts # 템플릿 API │ │ │ └── utils/ │ ├── validation.ts # Zod 스키마 │ └── formatters.ts # 데이터 포맷팅 │ └── types/ └── item.ts # 품목 관련 타입 ``` ### 4.2 파일별 역할 #### Server Components (기본) ```typescript // src/app/[locale]/(protected)/items/page.tsx import { fetchItems } from '@/lib/api/items'; export default async function ItemsPage() { // 서버에서 직접 데이터 fetching const items = await fetchItems(); return (

품목 목록

{/* Client Component로 전달 */}
); } ``` #### Client Components ('use client') ```typescript // src/components/items/ItemForm.tsx 'use client' import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useItemStore } from '@/stores/itemStore'; export default function ItemForm() { const { addItem } = useItemStore(); const form = useForm({ resolver: zodResolver(itemSchema), }); // 폼 제출, 이벤트 핸들러 등 } ``` --- ## 5. API 연동 전략 ### 5.1 Laravel API 엔드포인트 ```typescript // Laravel 백엔드 API const LARAVEL_API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'; // 품목 CRUD GET /api/items # 품목 목록 GET /api/items/:itemCode # 품목 상세 POST /api/items # 품목 등록 PUT /api/items/:itemCode # 품목 수정 DELETE /api/items/:itemCode # 품목 삭제 // BOM 관리 GET /api/items/:itemCode/bom # BOM 목록 GET /api/items/:itemCode/bom/tree # BOM 계층구조 POST /api/items/:itemCode/bom # BOM 추가 PUT /api/items/:itemCode/bom/:lineId # BOM 수정 DELETE /api/items/:itemCode/bom/:lineId # BOM 삭제 // 파일 업로드 POST /api/items/:itemCode/files # 파일 업로드 DELETE /api/items/:itemCode/files/:type # 파일 삭제 ``` ### 5.2 API 클라이언트 구현 ```typescript // src/lib/api/items.ts import type { ItemMaster } from '@/types/item'; const API_URL = process.env.NEXT_PUBLIC_API_URL; export async function fetchItems(params?: { itemType?: string; search?: string; category1?: string; }): Promise { const queryParams = new URLSearchParams(params as any); const response = await fetch(`${API_URL}/api/items?${queryParams}`, { headers: { 'Authorization': `Bearer ${getToken()}`, }, }); if (!response.ok) { throw new Error('Failed to fetch items'); } const data = await response.json(); return data.data; } export async function createItem(item: Partial): Promise { const response = await fetch(`${API_URL}/api/items`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}`, }, body: JSON.stringify(item), }); if (!response.ok) { const error = await response.json(); throw new Error(error.message || 'Failed to create item'); } const data = await response.json(); return data.data; } // 나머지 CRUD 함수들... ``` ### 5.3 Server Component에서 API 호출 ```typescript // src/app/[locale]/(protected)/items/page.tsx import { fetchItems } from '@/lib/api/items'; export default async function ItemsPage({ searchParams, }: { searchParams: { type?: string; search?: string }; }) { // 서버에서 직접 API 호출 (토큰은 쿠키에서 자동으로) const items = await fetchItems({ itemType: searchParams.type, search: searchParams.search, }); return (

품목 목록

); } ``` ### 5.4 Client Component에서 API 호출 ```typescript // src/components/items/ItemForm.tsx 'use client' import { createItem } from '@/lib/api/items'; import { useRouter } from 'next/navigation'; export default function ItemForm() { const router = useRouter(); const handleSubmit = async (data: ItemMaster) => { try { await createItem(data); router.push('/items'); router.refresh(); // Server Component 재검증 } catch (error) { console.error('Failed to create item:', error); } }; // 폼 렌더링... } ``` --- ## 6. 마이그레이션 계획 ### 6.1 단계별 계획 (수정됨) **Phase 1: 프로젝트 기반 구축** (2-3일) ``` ✅ Next.js 15 프로젝트 구조 이해 ✅ 데이터 구조 확정 (TypeScript 타입) ⏳ Laravel API 스펙 확인 ⏳ 환경 변수 설정 (.env.local) ⏳ 인증 토큰 관리 전략 ``` **Phase 2: 공통 컴포넌트 마이그레이션** (3-4일) ``` ⏳ shadcn/ui 컴포넌트 확인 (이미 설치됨) ⏳ 공통 폼 컴포넌트 (Input, Select, Button 등) ⏳ 레이아웃 컴포넌트 (PageHeader, FormActions 등) ⏳ 유효성 검사 (Zod 스키마 작성) ``` **Phase 3: Zustand Store 구성** (2-3일) ``` ⏳ itemStore.ts 작성 - addItem, updateItem, deleteItem - 클라이언트 상태 관리 ⏳ templateStore.ts 작성 (선택적) ⏳ 타입 정의 (types.ts) ``` **Phase 4: 물리적 페이지 구축** (5-6일) ``` ⏳ 품목 목록 페이지 (Server Component) ⏳ 품목 등록 페이지 ⏳ 품목 상세 페이지 ⏳ 품목 수정 페이지 ⏳ BOM 관리 컴포넌트 ⏳ 절곡품 전개도 입력 ⏳ 파일 업로드 ``` **Phase 5: Laravel API 연동** (3-4일) ``` ⏳ API 클라이언트 함수 작성 ⏳ 에러 처리 ⏳ 로딩 상태 관리 ⏳ 낙관적 업데이트 (Optimistic UI) ⏳ revalidation 전략 ``` **Phase 6: 테스트 및 최적화** (2-3일) ``` ⏳ 기능 테스트 ⏳ 성능 최적화 ⏳ UI/UX 개선 ⏳ 버그 수정 ``` **Phase 7: 동적 템플릿 시스템 (선택적)** (3-4일) ``` ⏳ ItemPage 템플릿 렌더링 ⏳ 동적 필드 생성 ⏳ 조건부 표시 로직 ⏳ 템플릿 관리 페이지 ``` **Phase 8: 배포 준비** (1-2일) ``` ⏳ 프로덕션 빌드 테스트 ⏳ 환경 변수 설정 ⏳ 문서 최종 검토 ``` **총 예상 소요 기간: 21-30일** ### 6.2 우선순위 매트릭스 ``` ┌─────────────────────────────────────────────────────┐ │ 높은 우선순위 (즉시 시작) │ ├─────────────────────────────────────────────────────┤ │ 1. 타입 정의 (types/item.ts) │ │ 2. API 클라이언트 (lib/api/items.ts) │ │ 3. Zustand Store (stores/itemStore.ts) │ │ 4. 품목 목록 페이지 (Server Component) │ │ 5. 품목 등록 폼 (Client Component) │ ├─────────────────────────────────────────────────────┤ │ 중간 우선순위 │ ├─────────────────────────────────────────────────────┤ │ 6. BOM 관리 │ │ 7. 파일 업로드 │ │ 8. 품목 수정/삭제 │ │ 9. 검색/필터 │ ├─────────────────────────────────────────────────────┤ │ 낮은 우선순위 (나중에) │ ├─────────────────────────────────────────────────────┤ │ 10. 절곡품 전개도 (복잡도 높음) │ │ 11. 버전 관리 │ │ 12. 동적 템플릿 시스템 │ │ 13. 고급 검색 │ └─────────────────────────────────────────────────────┘ ``` --- ## 7. Zustand 상태 관리 ### 7.1 itemStore 구조 ```typescript // src/stores/itemStore.ts import { create } from 'zustand'; import type { ItemMaster } from '@/types/item'; interface ItemStore { // State items: ItemMaster[]; selectedItem: ItemMaster | null; isLoading: boolean; error: string | null; // Actions setItems: (items: ItemMaster[]) => void; addItem: (item: ItemMaster) => void; updateItem: (itemCode: string, updates: Partial) => void; deleteItem: (itemCode: string) => void; selectItem: (item: ItemMaster | null) => void; setLoading: (isLoading: boolean) => void; setError: (error: string | null) => void; // Helpers getItemByCode: (itemCode: string) => ItemMaster | undefined; getItemsByType: (itemType: string) => ItemMaster[]; } export const useItemStore = create((set, get) => ({ // Initial state items: [], selectedItem: null, isLoading: false, error: null, // Actions setItems: (items) => set({ items }), addItem: (item) => set((state) => ({ items: [...state.items, item], })), updateItem: (itemCode, updates) => set((state) => ({ items: state.items.map((item) => item.itemCode === itemCode ? { ...item, ...updates } : item ), })), deleteItem: (itemCode) => set((state) => ({ items: state.items.filter((item) => item.itemCode !== itemCode), })), selectItem: (item) => set({ selectedItem: item }), setLoading: (isLoading) => set({ isLoading }), setError: (error) => set({ error }), // Helpers getItemByCode: (itemCode) => { return get().items.find((item) => item.itemCode === itemCode); }, getItemsByType: (itemType) => { return get().items.filter((item) => item.itemType === itemType); }, })); ``` ### 7.2 사용 예시 ```typescript // Client Component에서 사용 'use client' import { useItemStore } from '@/stores/itemStore'; export default function ItemForm() { const { addItem, setLoading, setError } = useItemStore(); const handleSubmit = async (data: ItemMaster) => { setLoading(true); try { const newItem = await createItem(data); addItem(newItem); // Zustand store 업데이트 } catch (error) { setError(error.message); } finally { setLoading(false); } }; return
...
; } ``` --- ## 8. Server/Client Components ### 8.1 컴포넌트 분류 기준 #### Server Components (기본) - ✅ 데이터 fetching - ✅ DB 직접 접근 - ✅ 민감한 정보 처리 (API 키 등) - ✅ 큰 의존성 사용 (번들 크기 감소) **예시**: ```typescript // src/app/[locale]/(protected)/items/page.tsx // 'use client' 없음 = Server Component import { fetchItems } from '@/lib/api/items'; export default async function ItemsPage() { const items = await fetchItems(); return (

품목 목록

); } ``` #### Client Components ('use client') - ✅ 상호작용 (onClick, onChange 등) - ✅ 상태 관리 (useState, useEffect) - ✅ 브라우저 API (localStorage, window 등) - ✅ 이벤트 리스너 **예시**: ```typescript // src/components/items/ItemForm.tsx 'use client' import { useState } from 'react'; import { useForm } from 'react-hook-form'; export default function ItemForm() { const [isSubmitting, setIsSubmitting] = useState(false); const form = useForm(); return
...
; } ``` ### 8.2 하이브리드 패턴 ```typescript // Server Component (부모) // src/app/[locale]/(protected)/items/page.tsx import { fetchItems } from '@/lib/api/items'; import ItemListClient from '@/components/items/ItemListClient'; export default async function ItemsPage() { // 서버에서 데이터 fetching const items = await fetchItems(); return (
{/* Client Component에 데이터 전달 */}
); } // Client Component (자식) // src/components/items/ItemListClient.tsx 'use client' import type { ItemMaster } from '@/types/item'; interface Props { items: ItemMaster[]; } export default function ItemListClient({ items }: Props) { const [selectedItem, setSelectedItem] = useState(null); return (
{items.map((item) => (
setSelectedItem(item)}> {item.itemName}
))}
); } ``` ### 8.3 품목관리 컴포넌트 분류 | 컴포넌트 | 타입 | 이유 | |---------|------|------| | `items/page.tsx` | Server | 데이터 fetching | | `ItemListClient.tsx` | Client | 선택, 필터 상호작용 | | `ItemForm.tsx` | Client | 폼 입력, 유효성 검사 | | `BOMManager.tsx` | Client | 동적 추가/삭제 | | `BendingDiagramInput.tsx` | Client | Canvas 조작 | | `FileUpload.tsx` | Client | 파일 선택, 업로드 | --- ## 9. 주의사항 ### 9.1 Next.js 15 특이사항 #### App Router 라우팅 ```typescript // ❌ 잘못된 방법 (Pages Router) import { useRouter } from 'next/router'; // ✅ 올바른 방법 (App Router) import { useRouter } from 'next/navigation'; ``` #### 다국어 지원 (next-intl) ```typescript // src/app/[locale]/(protected)/items/page.tsx import { useTranslations } from 'next-intl'; export default function ItemsPage() { const t = useTranslations('Items'); return

{t('title')}

; // "품목 목록" } ``` #### 쿠키 기반 인증 ```typescript // Server Component에서 쿠키 자동 포함 export async function fetchItems() { // cookies는 자동으로 포함됨 const response = await fetch(`${API_URL}/api/items`); } // Client Component에서 수동 포함 const response = await fetch('/api/items', { credentials: 'include', // 쿠키 포함 }); ``` ### 9.2 성능 최적화 #### 1. Server Components 최대한 활용 ```typescript // ✅ 서버에서 데이터 fetching (빠름) export default async function ItemsPage() { const items = await fetchItems(); return ; } // ❌ 클라이언트에서 useEffect (느림) 'use client' export default function ItemsPage() { useEffect(() => { fetchItems().then(setItems); }, []); } ``` #### 2. 이미지 최적화 ```typescript import Image from 'next/image'; // ✅ Next.js Image 컴포넌트 사용 절곡품 전개도 ``` #### 3. 동적 임포트 ```typescript // 무거운 컴포넌트 지연 로딩 import dynamic from 'next/dynamic'; const BendingDiagramInput = dynamic( () => import('@/components/items/BendingDiagramInput'), { ssr: false } // 클라이언트에서만 렌더링 ); ``` ### 9.3 보안 #### CSRF 보호 ```typescript // Laravel API는 Sanctum CSRF 토큰 필요 const response = await fetch(`${API_URL}/api/items`, { method: 'POST', headers: { 'X-CSRF-TOKEN': getCsrfToken(), // Laravel Sanctum 토큰 'Content-Type': 'application/json', }, credentials: 'include', }); ``` #### 환경 변수 ```bash # .env.local NEXT_PUBLIC_API_URL=http://localhost:8000 LARAVEL_API_KEY=secret_key_here ``` ```typescript // ✅ 서버에서만 사용 const API_KEY = process.env.LARAVEL_API_KEY; // NEXT_PUBLIC_ 없음 // ✅ 클라이언트에서도 사용 const API_URL = process.env.NEXT_PUBLIC_API_URL; // NEXT_PUBLIC_ 있음 ``` --- ## 10. 다음 단계 ### 10.1 즉시 시작 가능한 작업 **1. 타입 정의 작성** ```bash # src/types/item.ts - ItemMaster 인터페이스 - BOMLine 인터페이스 - BendingDetail 인터페이스 ``` **2. API 클라이언트 작성** ```bash # src/lib/api/items.ts - fetchItems() - createItem() - updateItem() - deleteItem() ``` **3. Zustand Store 작성** ```bash # src/stores/itemStore.ts - 기본 상태 정의 - CRUD 액션 구현 ``` ### 10.2 마이그레이션 체크리스트 **환경 설정**: - [ ] Laravel API URL 설정 - [ ] 인증 토큰 관리 전략 - [ ] CORS 설정 확인 - [ ] 환경 변수 설정 **타입 정의**: - [ ] ItemMaster 타입 - [ ] BOMLine 타입 - [ ] BendingDetail 타입 - [ ] API 응답 타입 **공통 컴포넌트**: - [ ] 폼 컴포넌트 (react-hook-form) - [ ] 테이블 컴포넌트 - [ ] 모달 컴포넌트 - [ ] 파일 업로드 컴포넌트 **페이지 구현**: - [ ] 품목 목록 (Server Component) - [ ] 품목 등록 (Client Component) - [ ] 품목 상세 (하이브리드) - [ ] 품목 수정 (Client Component) **기능 구현**: - [ ] BOM 관리 - [ ] 절곡품 전개도 - [ ] 파일 업로드 - [ ] 검색/필터 --- ## 11. 참고 자료 ### 11.1 Next.js 15 공식 문서 - [App Router](https://nextjs.org/docs/app) - [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) - [Data Fetching](https://nextjs.org/docs/app/building-your-application/data-fetching) ### 11.2 라이브러리 문서 - [Zustand](https://zustand-demo.pmnd.rs/) - [React Hook Form](https://react-hook-form.com/) - [Zod](https://zod.dev/) - [next-intl](https://next-intl-docs.vercel.app/) ### 11.3 기술 스택 ```json { "프론트엔드": { "프레임워크": "Next.js 15.5.6", "라이브러리": "React 19.2.0", "언어": "TypeScript 5", "스타일링": "Tailwind CSS 4", "상태관리": "Zustand 5.0.8", "폼": "react-hook-form 7.66.0", "검증": "Zod 4.1.12", "다국어": "next-intl 4.4.0" }, "백엔드": { "프레임워크": "Laravel (PHP)", "데이터베이스": "PostgreSQL 또는 MySQL", "인증": "Laravel Sanctum", "스토리지": "로컬 또는 AWS S3" } } ``` --- ## 부록 ### A. 용어 정의 | 용어 | 설명 | |-----|------| | FG | Finished Goods (완제품) | | PT | Parts (부품) | | SM | Sub-Materials (부자재) | | RM | Raw Materials (원자재) | | CS | Consumables (소모품) | | BOM | Bill of Materials (자재명세서) | | RSC | React Server Components | | SSR | Server-Side Rendering | | CSR | Client-Side Rendering | ### B. 품목 코드 체계 **형식**: `{업체코드}-{품목유형}-{일련번호}` **예시**: - `KD-FG-001`: 케이디 제품 001번 - `KD-PT-001`: 케이디 부품 001번 - `KD-RM-001`: 케이디 원자재 001번 ### C. 문의 및 지원 마이그레이션 과정에서 질문이나 문제가 발생하면 이 문서를 참조하여 진행하세요. **세션 시작 시 전달 내용:** > "품목관리 마이그레이션 작업을 계속하고 싶습니다. Next.js 15 기준 ITEM_MANAGEMENT_MIGRATION_GUIDE.md 문서를 참조해주세요." --- **문서 끝**