1. 개요

DoEatFit 서비스가 v2.0으로 진화하며 프론트엔드의 확장성과 유지보수성을 극대화하기 위해, Next.js App Router 프레임워크로의 전격 마이그레이션을 단행했습니다. 단순한 프레임워크 교체를 넘어 모든 비동기 서버 상태 관리를 TanStack Query로 통합하고, Shadcn/ui와 Lucide React를 통해 디자인 시스템의 기틀을 닦으며 정립한 표준 컴포넌트 아키텍처와 협업 컨벤션의 기품 있는 기록입니다.


2. 핵심 내용

2-1. 🎯 아키텍처 패턴 및 파일 구조 표준화

  • 서버 상태 제어의 단일화: 리액트 쿼리(TanStack Query)를 서비스의 심장으로 삼아 모든 비동기 서버 통신 상태를 효율적으로 격리하고 관리하도록 규칙을 다졌습니다. useQueryuseMutation을 컴포넌트 내부에서 직접 호출하지 않고 커스텀 훅으로 한 단계 래핑하여 비즈니스 논리를 우아하게 분리했습니다.
  • 체계적인 파일 레이아웃 분리:
    • /apis/: Axios 인스턴스 기반의 순수 통신 함수들만 엄격히 모아둔 통로입니다. (예: calc-diet-api.ts, calc-hr-api.ts)
    • /hooks/: 리액트 쿼리 어노테이션이 접목된 비즈니스 전용 커스텀 훅 보관소입니다. (예: use-calc-diet.ts, use-calc-hr.ts)
    • /constants/: 성별, 활동 수준, 식단 목표 등 코드 전반에 쓰이는 상수 및 이넘(Enum)들을 모듈화했습니다.
    • /types/: 모든 API 요청/응답 스키마와 제네릭 페이지네이션(Page<T>) 규격을 깔끔하게 선언하여 프론트/백엔드 통신 무결성을 보장했습니다.

2-2. 🔄 페이지네이션 표준화 및 데이터 변환 패턴

  • Spring Boot Pageable 완벽 대응: 스프링 백엔드의 Pageable 스펙과 원활하게 바인딩되도록 0-based와 1-based 페이지 오프셋 변환을 안전하게 처리했습니다.
  • 수동 페이지네이션(manualPagination)과 React Table 설계:
const [pagination, setPagination] = useState<PaginationState>({
  pageIndex: 0,
  pageSize: 10,
});
 
const table = useReactTable({
  data: tableData.rows,
  columns,
  state: { pagination },
  rowCount: tableData.rowCount,
  pageCount: tableData.pageCount,
  manualPagination: true,
  onPaginationChange: setPagination,
  getCoreRowModel: getCoreRowModel(),
});
  • 데이터 구조의 매끄러운 맵핑:
const tableData = useMemo(() => {
  if (!historyData?.content) {
    return { rows: [], pageCount: 0, rowCount: 0 };
  }
  const rows = historyData.content.map((item, index) => ({
    ...item,
    id: historyData.number * historyData.size + index + 1, // 글로벌 고유 번호 생성
  }));
  return {
    rows,
    pageCount: historyData.totalPages || 0,
    rowCount: historyData.totalElements || 0,
  };
}, [historyData]);
  • Query Keys 아키텍처 구조화: 캐시가 충돌하거나 누수되지 않도록 도메인별 쿼리 키 팩토리를 구성해 전역 격리했습니다.
export const calculatorKeys = {
  all: ['calculator'] as const,
  results: () => [...calculatorKeys.all, 'results'] as const,
  result: (uuid: string) => [...calculatorKeys.results(), uuid] as const,
  history: (page: number, size: number) => [...calculatorKeys.all, 'history', page, size] as const,
} as const;

2-3. ✅ 최종 검증 및 모던 오픈소스 라이브러리 검토

  • 최종 검증:
    • 부드러운 화면 전환 UX: keepPreviousData: true 설정을 활용하여 페이지 번호를 빠르게 클릭해도 로딩 인디케이터로 화면이 출렁이지 않고, 이전 데이터가 은은하게 유지되다가 다음 데이터로 부드럽게 대체되는 우수한 페이지네이션 경험을 실증했습니다.
    • 공통 컴포넌트 신뢰도 확보: 행 클릭 시 해당 아이템의 디테일 뷰포트로 즉각 이동하는 쫀득한 라우팅 반응성을 프론트엔드 전반에서 검증해 냈습니다.
  • 추천 모던 오픈소스 라이브러리 검토:
    1. TanStack Query (React Query):
      • 평가: 클라이언트 상태와 완전히 성격이 다른 ‘서버 상태’를 안전하게 은닉, 캐싱, 동기화하기 위한 프론트엔드 표준 라이브러리로서 강력하게 추천합니다.
    2. Shadcn/ui (Radix UI):
      • 평가: 컴포넌트를 패키지 내부에 가두지 않고 프로젝트 폴더에 직접 소스 코드로 인젝션하여 테일윈드 CSS 클래스 디자인 자유도를 무제한으로 열어주는 최고의 디자인 프리셋입니다.
    3. Lucide React:
      • 평가: 성능에 악영향을 미치지 않는 트리 셰이킹(Tree Shaking)이 보장되는 슬림하고 직관적인 SVG 기반 고해상도 아이콘 라이브러리로 적극 추천합니다.