- LoadingSpinner 컴포넌트 5가지 변형 구현 - LoadingSpinner (인라인/버튼용) - ContentLoadingSpinner (상세/수정 페이지) - PageLoadingSpinner (페이지 전환) - TableLoadingSpinner (테이블/리스트) - ButtonSpinner (버튼 내부) - 18개+ 페이지 로딩 UI 표준화 - HR 페이지 (사원, 휴가, 부서, 급여, 근태) - 영업 페이지 (견적, 거래처) - 게시판, 팝업관리, 품목기준정보 - API 키 보안 개선 (NEXT_PUBLIC_API_KEY → API_KEY) - Textarea 다크모드 스타일 개선 - DropdownField Radix UI Select 버그 수정 (key prop) - 프로젝트 헬스 개선 계획서 문서화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
11 KiB
프로젝트 헬스 개선 계획서
작성일: 2025-12-19 최종 업데이트: 2025-12-20 목적: 프로젝트 구조, 성능, 안정성 개선
현황 요약
| 영역 | 상태 | 핵심 이슈 |
|---|---|---|
| 빌드 설정 | ✅ 해결됨 | |
| 상태관리 | 🟡 개선 필요 | ItemMasterContext 과부하 (13개 상태) |
| Next.js 활용 | 🔴 심각 | 259개 'use client', Server Component 미활용 |
| 디자인 일관성 | ✅ 완료 |
Phase 1: 긴급 (이번 주)
1.1 TypeScript 에러 해결 + ignoreBuildErrors 제거
현재 상태:
// next.config.ts
typescript: { ignoreBuildErrors: true } // 98개 에러 숨김
eslint: { ignoreDuringBuilds: true }
작업 내용:
Step 1: 타입 에러 카테고리 분류
| 카테고리 | 개수 | 예시 |
|---|---|---|
| 모델 타입 불일치 | ~26개 | Employee에 concurrentPosition 없음 |
| Props 미스매치 | ~35개 | IntegratedListTemplateV2 props 변경 |
| 배열 타입 불일치 | ~9개 | PricingListItem 타입 정의 |
| 기타 | ~28개 | - |
Step 2: 수정 순서
src/types/폴더의 모델 타입 정의 업데이트IntegratedListTemplateV2Props 인터페이스 정리- 페이지별 타입 에러 수정
ignoreBuildErrors: false변경npm run build성공 확인
예상 소요: 2-3시간
위험도: 🔴 높음 (빌드 실패 가능)
1.2 API 키 서버 사이드 이동
현재 상태:
# .env.local
NEXT_PUBLIC_API_KEY=42Jfwc6EaR... # 브라우저에서 노출됨!
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=... # 브라우저에서 노출됨!
문제점:
NEXT_PUBLIC_접두사 → 클라이언트 번들에 포함- 브라우저 개발자도구에서 확인 가능
- API 남용/해킹 위험
작업 내용:
Step 1: 환경변수 이름 변경
# .env.local (수정 후)
API_KEY=42Jfwc6EaR... # 서버만 접근
GOOGLE_MAPS_API_KEY=AIzaSyAS3bA... # 서버만 접근
# 클라이언트에서 필요한 공개 정보만
NEXT_PUBLIC_API_BASE_URL=https://api.example.com
Step 2: 서버 사이드 프록시 확인
// src/app/api/proxy/[...path]/route.ts
// 이미 구현됨 - API_KEY를 서버에서 주입
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`, // 서버에서만 접근
},
});
Step 3: Google Maps 처리
// 옵션 A: 서버 사이드 렌더링
// 옵션 B: API 라우트로 프록시
// 옵션 C: Maps Embed API 사용 (키 제한 설정)
예상 소요: 30분-1시간
위험도: 🟡 중간 (dev 서버 재시작 필요)
1.3 ThemeContext SSR 수정
현재 상태:
// src/contexts/ThemeContext.tsx
const [theme, setThemeState] = useState<Theme>("light");
useEffect(() => {
const savedTheme = localStorage.getItem("theme"); // SSR에서 에러 가능
if (savedTheme) {
setThemeState(savedTheme);
}
}, []);
문제점:
- 서버에서
localStorage접근 시 에러 - Hydration mismatch 발생 가능
작업 내용:
수정 코드
// src/contexts/ThemeContext.tsx (수정 후)
const [theme, setThemeState] = useState<Theme>(() => {
// SSR 안전 체크
if (typeof window === 'undefined') return 'light';
const savedTheme = localStorage.getItem('theme');
return (savedTheme as Theme) || 'light';
});
// 또는 useEffect 패턴 유지 (더 안전)
const [theme, setThemeState] = useState<Theme>('light');
const [isHydrated, setIsHydrated] = useState(false);
useEffect(() => {
setIsHydrated(true);
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
setThemeState(savedTheme as Theme);
}
}, []);
예상 소요: 15분
위험도: 🟢 낮음 (HMR 즉시 반영)
Phase 2: 단기 (2주)
2.1 ItemMasterContext 분할
현재 상태:
ItemMasterContext
├── 품목 데이터 (3개 상태)
├── 기준정보 (7개 상태)
├── 폼 구조 (4개 상태)
└── 50개+ 메서드
→ ANY 상태 변경 시 전체 리렌더링
개선 방향:
ItemMasterDataContext → 품목 기본 데이터
ItemFormContext → 페이지/섹션/필드 구조
ItemLookupContext → 단위/재질/처리방식 등 기준정보
작업 내용:
- Context 분할 설계
- 각 Context별 Provider 구현
- 기존 useItemMaster → 새 Context hooks로 마이그레이션
- 테스트
예상 소요: 1-2일
위험도: 🟡 중간 (기존 코드 변경 필요)
2.2 IntegratedListTemplate → Zustand Store
현재 상태:
// 20개+ props 전달
<IntegratedListTemplateV2
searchValue={searchValue}
onSearchChange={setSearchValue}
currentPage={currentPage}
onPageChange={setCurrentPage}
selectedItems={selectedItems}
onSelectionChange={setSelectedItems}
// ... 더 많은 props
/>
개선 방향:
// Zustand store
const useListStore = create((set) => ({
// 페이지네이션
currentPage: 1,
pageSize: 20,
setPage: (page) => set({ currentPage: page }),
// 필터/검색
searchValue: '',
filters: {},
setSearch: (value) => set({ searchValue: value }),
// 선택
selectedIds: new Set(),
toggleSelection: (id) => set((state) => { /* ... */ }),
}));
// 컴포넌트에서 사용
function MyListPage() {
const { currentPage, setPage } = useListStore();
return <IntegratedListTemplateV2 />; // props 최소화
}
작업 내용:
src/store/listStore.ts생성- 공통 리스트 상태 추출
- 페이지별 점진적 마이그레이션
- IntegratedListTemplateV2 리팩토링
예상 소요: 2-3일
위험도: 🟡 중간
2.3 다크모드 스타일 완성
현재 상태:
// Button - 일부 variant만 dark: 정의
ghost: "hover:bg-accent hover:text-accent-foreground" // dark: 없음
outline: "border-input bg-background" // dark: 없음
작업 내용:
- 모든 UI 컴포넌트 다크모드 스타일 점검
- Button, Select, Input 등 주요 컴포넌트 수정
- 색상 대비 검증 (WCAG AA 기준)
- 다크모드 테스트
예상 소요: 1일
위험도: 🟢 낮음
Phase 3: 중기 (1개월)
3.1 주요 페이지 Server Component 전환
현재 상태:
- 259개 'use client' 컴포넌트
- 모든 데이터 페칭: useEffect 내 클라이언트 fetch
- 초기 로딩 지연
개선 방향:
// Before (Client Component)
'use client';
export default function ItemPage() {
const [items, setItems] = useState([]);
useEffect(() => {
fetch('/api/items').then(r => r.json()).then(setItems);
}, []);
return <ItemList items={items} />;
}
// After (Server Component)
export default async function ItemPage() {
const items = await fetch('/api/items').then(r => r.json());
return <ItemList items={items} />; // 클라이언트로 props 전달
}
마이그레이션 우선순위:
- 정적 페이지 (설정, 정보 페이지)
- 리스트 페이지 (items, employees)
- 상세 페이지
예상 소요: 1-2주
위험도: 🟡 중간
3.2 캐싱 전략 수립
현재 상태:
cache: 'no-store' // 모든 fetch에 적용 → 성능 저하
개선 방향:
| 데이터 유형 | 캐싱 전략 | TTL |
|---|---|---|
| 정적 데이터 (카테고리, 단위) | force-cache |
1시간 |
| 사용자 데이터 | no-store |
- |
| 리스트 데이터 | revalidate: 60 |
1분 |
| 상세 데이터 | revalidate: 300 |
5분 |
작업 내용:
- 데이터 유형별 분류
- fetch 옵션 표준화
- revalidateTag/revalidatePath 활용
- 성능 측정
예상 소요: 3-5일
위험도: 🟢 낮음
3.3 TanStack Query 도입 검토
도입 이점:
- API 상태 자동 관리 (loading, error, data)
- 캐싱 및 백그라운드 리페치
- 낙관적 업데이트
- DevTools 지원
도입 시 구조:
// hooks/useItems.ts
export function useItems() {
return useQuery({
queryKey: ['items'],
queryFn: () => itemApi.getAll(),
staleTime: 5 * 60 * 1000, // 5분
});
}
// 컴포넌트에서 사용
function ItemList() {
const { data, isLoading, error } = useItems();
// 자동으로 loading/error 처리
}
검토 포인트:
- 현재 API 호출 패턴 분석
- 도입 시 마이그레이션 범위
- 번들 사이즈 영향 (~20KB)
- 팀 학습 비용
예상 소요: 1-2주 (검토 + 파일럿)
위험도: 🟡 중간 (큰 변화)
체크리스트 요약
Phase 1 (긴급) ⏰ ✅ 완료 (2025-12-20)
- 1.1 타입 에러 해결 + ignoreBuildErrors 제거 ✅
- 98개 → 0개 에러 수정
npx tsc --noEmit성공npm run build성공
- 1.2 API 키 서버 사이드 이동 ✅
NEXT_PUBLIC_API_KEY→API_KEY변경- 프록시 라우트에서 서버 사이드 주입
- 1.3 ThemeContext SSR 수정 ✅
typeof window체크 추가
Phase 2 (단기) 📅
- 2.1 ItemMasterContext 3개로 분할
- 2.2 IntegratedListTemplate → Zustand store
- 2.3 다크모드 스타일 완성 ✅ (2025-12-20)
- Textarea: Input과 스타일 통일 (
dark:bg-input/30추가) - 모든 UI 컴포넌트 다크모드 지원 확인:
- Button, Select, Input ✅ (dark: 스타일 적용됨)
- Card, Dialog, Sheet, Popover ✅ (CSS 변수로 처리)
- Table, DropdownMenu ✅ (CSS 변수 + dark: 스타일)
- Badge, Checkbox, RadioGroup, Switch ✅ (dark: 스타일 적용됨)
- Alert, Tabs ✅ (CSS 변수 + dark: 스타일)
- Textarea: Input과 스타일 통일 (
- 2.4 로딩 스피너 표준화 ✅ (2025-12-20)
loading-spinner.tsx5가지 변형 컴포넌트 구현:LoadingSpinner: 인라인/버튼용 (xs, sm, md, lg 사이즈)ContentLoadingSpinner: 상세/수정 페이지용 (min-h-[400px])PageLoadingSpinner: 페이지 전환용 (min-h-[calc(100vh-200px)])TableLoadingSpinner: 테이블/리스트용 (py-16)ButtonSpinner: 버튼 내부 스피너
- 18개+ 페이지 표준화 적용:
- HR 페이지 (사원, 휴가, 부서, 급여, 근태관리)
- 품목기준정보관리, 게시판, 팝업관리
- 견적관리 상세/수정
- 빌드 테스트 성공 (231 pages)
Phase 3 (중기) 📆
- 3.1 주요 페이지 Server Component 전환
- 3.2 캐싱 전략 수립
- 3.3 TanStack Query 도입 검토
참고 자료
- 분석 리포트: 2025-12-19 프로젝트 헬스체크
- 관련 문서:
claudedocs/guides/[GUIDE-2025-12-16] options-vs-flattened-data.mdclaudedocs/guides/[PLAN-2025-12-19] page-layout-standardization.md