Vercel React 코드 감사 및 개선 결과

  • 기준일: 2026-07-22
  • 기준: vercel-react-best-practices
  • 범위: src/routes, 각 feature의 pages/components/hooks/domain/store, src/shared/ui
  • 상태: 구현 및 검증 완료

적용 기준

이번 감사에서는 현재 Vite + React + TanStack Router/Query 구조에 직접 적용되는 항목을 우선했습니다.

  • 배럴 import로 인한 불필요한 모듈 로딩 제거
  • 큰 라이브러리와 개발 전용 기능의 지연 로딩
  • 서로 독립적인 비동기 작업의 병렬 처리
  • 페이지 최상위의 과도한 상태 구독과 리렌더링 격리
  • 요청 취소, 중복 요청 방지, 타이머 정리
  • 로딩·에러·데이터 동기화 시 화면 일관성 보장
  • 한 번만 필요한 값의 lazy 초기화

번들 결과

항목변경 전변경 후결과
초기 app.js405.95 kB / gzip 129.98 kB347.60 kB / gzip 112.98 kBgzip 약 17.00 kB 감소
이미지 저장 라이브러리초기 그래프에 포함별도 청크 13.00 kB / gzip 5.19 kB저장할 때만 로드
Live Studio 관련 청크 합계161.92 kB / gzip 51.34 kB141.37 kB / gzip 43.89 kB개발용 QA 코드 운영 번들 제외

라우트 자동 분할은 TanStack Router의 autoCodeSplitting: true가 이미 담당하므로 페이지에 별도 React.lazy를 중복 적용하지 않았습니다.

페이지별 점검

페이지·라우트판정적용 내용
Root (__root)수정 완료공용 UI 배럴 import를 직접 import로 교체해 모든 페이지 초기 청크가 전체 UI를 끌어오지 않도록 함
로그인적합직접 import로 경계를 명확히 했고, 현재 렌더·요청 구조는 추가 메모이제이션이 불필요함
Google 로그인 완료수정 완료OAuth 완료 요청을 React Query로 단일화하고 StrictMode에서도 POST와 이동이 중복되지 않게 함
TAPLE 소개적합직접 import로 정리했으며 정적 콘텐츠 중심이라 추가 상태 최적화는 불필요함
Live Studio수정 완료프로필 폼 동기화와 블록 배열 구독을 하위 컴포넌트로 격리해 입력 때 페이지 전체가 리렌더링되지 않게 함
Live Studio수정 완료미저장 작업을 모두 비운 뒤 미리보기로 이동하며 저장 실패 시 현재 화면에 머물도록 함
Live Studio수정 완료QA 도구를 DEV 환경에서만 동적 import해 운영 번들에서 제거함
Live Preview수정 완료서버 명함과 로컬 초안의 identity/version을 비교해 최신 초안을 보존하고, 동기화 전 기본 명함이 잠깐 보이는 현상을 제거함
Live Preview수정 완료pending/error/404를 Skeleton, ErrorState, NotFoundState로 명확히 분기함
공개 명함수정 완료저장 기능에서만 html-to-image를 불러오고 앞·뒤 이미지 생성을 병렬 처리함
공개 명함수정 완료로그아웃 시 인증 정리와 초안 스토어 로드를 병렬 처리하고 무거운 스토어를 초기 공개 라우트에서 분리함
NFC 진입수정 완료인증 여부를 lazy state로 한 번 읽고, 계정 전환 시 로그아웃·스토어 로드를 병렬 처리함
마이페이지수정 완료기존 병렬 조회는 유지하고 로그아웃 시 Live Studio 초안도 함께 초기화해 다음 계정으로 상태가 새지 않게 함
관리자수정 완료검색에 300ms debounce를 적용하고 요청 AbortSignal을 API까지 전달함
관리자수정 완료펼치지 않은 회원 행은 상세 query observer 자체를 만들지 않도록 상세 영역을 분리함
사용 불가 명함적합작은 정적 안내 라우트로 유지하고 직접 import만 적용함

컴포넌트별 점검

Live Studio

컴포넌트판정확인·수정 내용
BlockEditorSection적합DnD와 블록 상태가 편집 영역 안에 머물며 공개 페이지 번들에서는 분리됨
CardCustomizeSheet적합단일 UI 책임을 유지하며 불필요한 상태 복제 없음
CardImageExporter수정 완료imperative handle 의존성을 고정하고 실제 저장 시점에만 변환 모듈 사용
CardPreviewSection적합현재 입력만 받아 표현하며 별도 중복 상태 없음
GalleryThumbnail적합작은 표시 컴포넌트로 유지
ImageViewer적합페이지 상태와 분리된 뷰어 책임 유지
LinkPlatformIcon적합직접 아이콘 import로 불필요한 아이콘 모듈 로딩 방지
LiveStudioDevTools수정 완료운영 코드 경로에서 제외
LiveStudioDevToolsContainer신규QA query/mutation 구독을 개발 전용 경계 안으로 이동
LiveStudioSkeleton적합레이아웃 크기를 유지하는 로딩 UI 사용
PrivateBusinessCardNotice적합개인정보를 조회하지 않는 비공개 상태 표현 유지
ProfileEditModal적합폼 책임을 모달 내부에 유지
ProfileEditorSection수정 완료상위 페이지 대신 전용 동기화 컴포넌트가 폼 값을 구독하도록 변경
PublicBusinessCardSkeleton적합공개 화면과 미리보기에서 공통 사용
PublicCardHeader적합방문자 상태별 액션만 표현하며 데이터 요청 없음
PublicLinkShareBar수정 완료복사 완료 타이머를 재실행·unmount 때 정리
StudioSummary적합표시 책임만 유지하고 상태 구독 없음

Admin

컴포넌트판정확인·수정 내용
ConnectCardDialog적합mutation 상태를 상위 훅에서 받아 표시하는 구조 유지
DashboardSummary적합계산된 요약 데이터를 표현만 함
IssueCardDialog적합단일 폼 책임 유지
UnconnectedCardRegistry적합로컬 목록 필터에는 useDeferredValue를 유지하고 서버 검색과 역할을 분리함
UserCardTable수정 완료펼친 행에서만 상세 컴포넌트와 query를 mount함

Login, My Page, Introduction

컴포넌트판정확인·수정 내용
GoogleLoginButton적합이벤트 기반 OAuth 시작 구조 유지
LoginHero적합정적 표현 컴포넌트로 유지
AccountManagementPanel적합계정 액션만 전달받아 표현
AccountProfileCard적합서버 상태를 복제하지 않고 props로 표현
CardStatusModePanel적합공개 상태 mutation 책임이 페이지 훅에 유지됨
MyPageSkeleton적합실제 레이아웃과 유사한 로딩 크기 유지
NfcCardPanel적합NFC 상태 표현 책임 유지
InstructionList적합정적 목록 표현
IntroLogo적합정적 로고 표현
ShareGuideBottomSheet적합열림 상태를 상위에서 제어하는 controlled 구조 유지
TapleCardIllustration적합정적 SVG로 유지

Shared UI

컴포넌트판정확인·수정 내용
AsyncState적합Error/NotFound 공통 표현 유지
BlockCard적합단일 카드 표시 책임 유지
Button수정 완료아이콘 직접 import 적용
ConfirmDialog적합대화상자 상태를 외부에서 제어
Header수정 완료아이콘 직접 import 적용
Icon적합개별 아이콘 export 구조 유지
ImageSlider적합현재 운영 사용처 없음. 추측성 리팩터링은 하지 않음
Input수정 완료아이콘 직접 import 적용
Menu적합메뉴 상태를 내부에 국소화
NameCard적합표시 로직을 한 컴포넌트 경계에 유지
Profile적합프로필 표현 책임 유지
Skeleton적합reduced-motion 대응 유지
Switch적합controlled input 구조 유지
Toast수정 완료아이콘 직접 import 적용

재발 방지

운영 코드가 다시 @/shared/ui 또는 feature 최상위 배럴을 import하면 ESLint가 실패하도록 규칙을 추가했습니다. 테스트와 Storybook은 편의성을 위해 이 제한에서 제외합니다.

의도적으로 보류한 항목

항목이유후속 조건
마이페이지와 Live Studio의 /me/business-card query 통합두 화면이 같은 endpoint에 서로 다른 응답 타입을 기대하고 있어 query key만 합치면 캐시 shape 충돌 위험이 있음백엔드 응답 계약과 프론트 DTO를 하나로 통일한 뒤 진행
공개 명함 route loader prefetch공개 명함은 비공개 전환을 즉시 반영하려고 gcTime: 0, refetchOnMount: always 정책을 사용 중공개 데이터 캐시·개인정보 신선도 정책을 먼저 확정한 뒤 검토
폰트 자산 축소디자인 지정 폰트를 그대로 유지한다는 제품 결정을 존중폰트 디자인 정책이 바뀔 때만 subset 검토
사용하지 않는 ImageSlider의 포인터 이벤트 리팩터링운영 경로의 성능에 영향이 없고 현재 요청 범위를 벗어남실제 사용 페이지가 생기고 프로파일링에서 병목이 확인될 때 진행

검증

  • pnpm quality:check: 타입 검사, ESLint, 47개 테스트 파일·236개 테스트, 3개 빌드 계약 테스트 통과
  • pnpm analyze: 번들 분석 빌드 통과
  • pnpm build: 클라이언트·SSR·prerender·공개 자산 계약 검증 통과
  • 운영 산출물에서 QA 카드 생성 문구와 개발 도구 코드가 포함되지 않음을 확인