SEMT 제품 관리 시스템
2025.11 ~ 현재프론트엔드 개발
의료기기 자산 관리 웹 애플리케이션.
- Next.js 16
- React 19
- TypeScript 5
- TanStack Query v5
- Zod
- React Hook Form
- Zustand
- Biome
- pnpm
전체 CSR → Streaming SSR 아키텍처 전환
.problem
모든 데이터 페칭이 클라이언트 사이드('use client')에서 이루어져 초기 페이지 렌더링 시 빈 화면이 노출되는 구조였음. 인증 토큰을 localStorage에 저장해 서버 컴포넌트에서 접근이 불가능했고, SSR 프리페치 후에도 데이터가 느릴 경우 전체 페이지 렌더링이 블로킹되며 에러 상황의 fallback UI도 부재했음.
.solution
prefetchQuery + HydrationBoundary + useSuspenseQuery 패턴 도입으로 서버사이드 프리페치 구현. 토큰 저장소를 localStorage → Cookie로 이관하고 set-cookie Route Handler를 신규 생성하여 서버 컴포넌트에서 쿠키 접근 가능하도록 캡슐화. 6개 페이지를 page.tsx(서버)와 *-content.tsx(클라이언트)로 역할 분리. 이후 @suspensive/react-query의 QueriesHydration을 활용한 Streaming SSR로 고도화하여 각 페이지 layout에 Suspense + ErrorBoundary를 추가하고 여러 쿼리를 단일 Streaming 경계로 묶음.
.tradeoff
- advantages
- 서버에서 데이터를 미리 페칭해 HTML에 포함하므로 초기 로딩 시 빈 화면 제거. Streaming으로 준비된 데이터부터 순차 렌더링하여 체감 속도 개선. 클라이언트 번들 의존도 감소.
- disadvantages
- 서버/클라이언트 컴포넌트 경계를 명시적으로 관리해야 하는 복잡도 증가. @suspensive/react-query와 TanStack Query v5의 버전 호환성 주의 필요.
- rationale
- localStorage 기반 CSR은 서버 컴포넌트 활용이 불가능하고, 빈 화면 노출로 실사용자 피드백이 부정적이었음. 토큰 저장소 교체를 Route Handler로 캡슐화하면 클라이언트 코드 변경 없이 SSR 전환이 가능하다고 판단.
.result
6개 페이지 SSR 구조 전환. 클라이언트 번들 의존도 대폭 감소.
.learning
Next.js App Router에서 SSR을 적용할 때 localStorage → Cookie 교체가 선행 과제임. API Route Handler로 토큰 설정을 캡슐화하면 클라이언트 코드 변경을 최소화할 수 있음. QueriesHydration이 복수 prefetchQuery를 단일 Suspense 경계로 감싸는 추상화를 이해하면 Streaming SSR 보일러플레이트 없이 적용 가능.
Zod + React Hook Form 기반 폼 유효성 검증 체계화
.problem
기존 폼 로직이 useState로 formData를 관리하는 방식이어서 각 필드의 유효성 검증이 컴포넌트마다 분산되고, 오류 메시지 기준이 일관되지 않았음. PC 등록, QC, 출하, 설치, 수리 등 10개 이상의 복잡한 탭 폼이 동일한 필드임에도 서로 다른 검증 로직을 가지고 있었음.
.solution
Zod 스키마 기반 타입 안전 유효성 검증과 React Hook Form으로 폼 상태 관리를 일원화. 스키마 파일 9개를 신규 생성하여 검증 로직을 단일 소스화. @hookform/resolvers로 Zod와 RHF를 연결. 스키마 파일을 먼저 분리한 뒤 컴포넌트 단위로 점진적으로 적용하여 리그레션 위험 최소화.
.tradeoff
- advantages
- z.infer<typeof schema>로 타입 자동 추론하여 타입 중복 선언 제거. 스키마 파일 분리로 동일 필드의 유효성 기준이 단일 소스화되어 테스트 가능성과 재사용성 향상.
- disadvantages
- React Hook Form은 uncontrolled 컴포넌트 방식이므로 기존 controlled state 방식과 혼용 불가. 전면 교체 시 리그레션 위험이 존재.
- rationale
- 점진적 마이그레이션(스키마 파일 먼저 분리 → 컴포넌트 단위 적용)으로 리그레션 위험을 최소화하면서도 검증 로직 중앙화의 이점을 확보할 수 있다고 판단.
.result
스키마 9개로 중앙화하여 18개 폼 컴포넌트의 유효성 기준 통일.
.learning
유효성 검증 로직을 컴포넌트 바깥 스키마 파일로 분리하면 테스트 가능성과 재사용성이 동시에 향상됨. z.infer<typeof schema>를 통한 타입 자동 추론으로 타입 중복 선언을 제거하는 패턴이 특히 폼이 많은 관리 시스템에서 효과적.
프로덕션 환경 런타임 설정 동적 로드 시스템 설계
.problem
하드코딩된 process.env.NEXT_PUBLIC_IMAGE_BASE_URL 사용으로 인해 Docker 컨테이너 배포 시 빌드 시점의 환경 변수가 고정되어, 배포 환경에 따라 이미지가 로드되지 않는 문제 발생. 개발 환경에서 빌드한 이미지를 프로덕션에 배포하면 개발 서버 URL이 하드코딩되어 이미지를 찾을 수 없었음.
.solution
/api/config 엔드포인트를 통해 런타임에 환경 변수를 동적으로 fetch하는 시스템 구축. EnvManager 싱글톤 패턴으로 환경 설정을 중앙 관리. getEnv() 유틸리티 함수로 모든 컴포넌트에서 일관된 방식으로 환경 변수 접근.
.tradeoff
- advantages
- 한 번의 빌드로 모든 배포 환경 대응 가능 (개발/프로덕션), Docker 컨테이너 재빌드 없이 환경 변수만 변경하여 배포 가능, CI/CD 파이프라인 간소화
- disadvantages
- 초기 로딩 시 추가 HTTP 요청 발생 (약 50ms), 환경 변수 초기화 전 컴포넌트 렌더링 시 에러 가능성
- rationale
- 배포 유연성과 CI/CD 효율성이 초기 로딩 비용보다 중요
.result
배포 환경별 재빌드 불필요, Docker 이미지 재사용 가능. API 응답 시간 < 50ms로 사용자 체감 지연 없음.
.learning
빌드 타임 vs 런타임 설정의 트레이드오프 이해. 컨테이너 기반 배포 환경에서의 환경 변수 관리 전략. 싱글톤 패턴을 활용한 전역 상태 관리 및 초기화 타이밍 제어.