대규모 서비스의 전역 저장소 검토와 초기 가설의 한계
여기어때 서비스웹개발팀은 PC 웹, 모바일 웹, 앱 인앱 웹뷰를 단일 코드베이스로 아우르는 대규모 항공 프론트엔드 개편 프로젝트를 진행하면서, 초기에는 Zustand나 Recoil 등 범용 클라이언트 전역 상태 관리 라이브러리 도입을 우선적으로 검토했습니다. 검색, 검색 결과, 상품 상세, 예약, 결제, 마이페이지에 이르는 방대한 화면 범위와 국내선·해외선 분기, 깊어지는 컴포넌트 계층 구조로 인해 발생할 수밖에 없는 속성 내려보내기(Props Drilling) 문제를 해결하기 위한 조치였습니다.
그러나 개발팀은 라이브러리 선정 과정에서 기술 스펙 비교에 앞서 어떤 상태를 해당 저장소에 담을지 명확히 정의하지 못했다는 점을 파악했습니다. 화면의 개수나 컴포넌트 깊이라는 외형적 '규모'에 매몰되었던 초기 가설을 거두고, 상태가 본질적으로 언제까지 존속해야 하며 누구에게 소유권이 있는지를 규명하는 방향으로 아키텍처 설계 방향을 선회했습니다.
소유권과 생명주기에 기반한 상태의 4분류 체계
개발팀은 모든 데이터를 포괄하는 중앙 집중형 클라이언트 저장소를 배제하고 상태를 소유권에 따라 네 가지 영역으로 분할했습니다. 첫째는 항공편 목록, 예약 내역, 쿠폰 등 서버가 소유권을 쥔 '서버 상태'입니다. 이는 원격 서버 데이터의 클라이언트 사본에 불과하므로 데이터 동기화와 캐싱을 전담하는 TanStack Query(React Query)에 위임했습니다. 둘째는 검색 조건, 필터링 옵션, 정렬 기준처럼 사용자의 탐색 맥락을 대변하는 'URL 파라미터 상태'로, nuqs 라이브러리를 통해 주소창(Query String)이 직접 상태를 들고 있도록 구성했습니다.
셋째는 예약 폼 입력 단계나 아코디언 메뉴의 펼침 여부처럼 단일 화면 내부의 복수 컴포넌트가 공유하는 '화면 단위 UI 상태'로, React 내장 Context API를 활용했습니다. 마지막으로 개별 컴포넌트 내부에서만 소비되는 토글 및 임시 입력값은 기본 훅인 useState로 격리했습니다. 이 네 축으로 역할을 나누자 범용 전역 저장소가 담당해야 할 데이터 영역이 완전히 사라졌습니다.
URL 중심 상태 관리와 다중 플랫폼 동기화
항공권 검색 결과와 같은 화면에서 필터나 정렬 기준을 클라이언트 내부 상태로 관리할 경우 새로고침 시 초기화, 브라우저 뒤로가기 탐색 불일치, 링크 공유 시 탐색 조건 유실이라는 고질적인 문제가 발생합니다. nuqs를 적용해 URL 쿼리 파라미터를 단일 진실 공급원(Single Source of Truth)으로 삼는 방식은 이러한 문제를 브라우저의 기본 네비게이션 메커니즘을 통해 자연스럽게 해결합니다.
특히 웹과 인앱 웹뷰를 단일 코드로 구축한 환경에서 URL 상태 관리는 플랫폼 간 격차를 해소하는 중요한 통로로 작동합니다. 앱 웹뷰에서 탐색하던 항공권 필터 URL을 일반 웹 브라우저로 복사해 열더라도 완전히 동일한 검색 결과와 정렬 상태를 재현할 수 있습니다. 이는 복잡한 네이티브 브릿지나 별도의 저장소 동기화 로직 없이도 웹 표준 인터페이스를 활용해 플랫폼 간 일관성을 확보한 모범적인 실무 설계로 평가됩니다.
훅 직접 호출 아키텍처를 지탱하는 두 가지 전제 조건
가장 복잡한 화면인 해외선 예약 상세의 경우, 하위 컴포넌트 트리의 80곳 이상에서 `useSuspenseBookingDetail()` 훅을 직접 호출해 데이터를 조회합니다. TanStack Query의 요청 중복 제거(Request Deduplication) 메커니즘 덕분에 실제 네트워크 요청은 최초 1회만 발생하며, 중간 단계 컴포넌트들이 불필요한 props를 전달하기만 하는 릴레이 현상이 제거되었습니다.
다만 개발팀은 이 같은 말단 훅 호출 패턴이 성립하기 위해 두 가지 필수 전제 조건이 충족되어야 한다고 명시했습니다. 첫째는 폴더 명명 규칙을 통해 재사용 가능한 공용 컴포넌트와 특정 화면 전용 컴포넌트를 엄격히 격리하는 것입니다. 전용 컴포넌트에 한해서만 상위 컨텍스트의 데이터 존재를 전제할 수 있기 때문입니다. 둘째는 루트 레벨에서 Suspense와 로딩·에러 가드를 일원화하여 성공 상태일 때만 하위 트리를 렌더링하도록 강제하는 것입니다. 상위 가드가 없다면 80여 개 컴포넌트가 각자 데이터 미수신 상태를 방어해야 하므로 코드 중복이 폭증하게 됩니다.
실무적 영향과 객관적 검증 한계
여기어때의 사례는 프론트엔드 프로젝트의 규모가 커지면 전역 상태 라이브러리가 필수적이라는 통념에 실질적인 반례를 제시합니다. 데이터의 원천이 서버인지, 브라우저 URL인지, UI 트리인지 분류함으로써 불필요한 상태 라이브러리 의존성을 제거하고 데이터 흐름의 추적성을 높였습니다. 상위 부모가 알고 자식만 쓰는 데이터는 Props로 내려보내고, 화면 전반의 데이터는 전용 훅과 Context로 분리하는 방식은 아키텍처의 단순성을 유지하는 데 유효합니다.
그러나 실무 적용 시 한계와 주의점도 확인됩니다. 전용 컴포넌트가 아닌 다목적 공용 UI 컴포넌트에는 이 패턴을 적용할 수 없으며 반드시 Props를 주입해야 합니다. 또한 기술블로그 본문에는 범용 저장소 배제를 통해 달성한 번들 크기 절감 수치나 렌더링 벤치마크, 결함 감소율 등 정량적인 성능 비교 지표가 공개되지 않았으므로, 아키텍처적 청결함이 런타임 성능에 미친 정량적 기여도는 추가적인 검증이 필요합니다.
출처
여기어때 기술블로그의 원출처와 본문에 표시한 참고자료를 바탕으로 한국어로 재구성했습니다. ‘ARCH.B 분석’은 자료를 읽고 덧붙인 편집 해석입니다.
항공 프론트엔드 구축기 (9/10): 쓰자고 해 놓고 안 썼습니다 (새 창)


.png&w=1200&q=75)