Case study

Privia Front · 호텔 POI 검색

frontend·234 commits·2025–2026

Architecture

pinch to zoom · drag to pan

Stack

  • Next.js 14
  • React 18
  • TypeScript
  • Tailwind CSS
  • Zustand
  • pnpm workspace

External integrations

  • Hotel POI Backend
  • Google Maps
  • GTM

Highlights

  • POI 검색 (Quick / Result / Recent)
  • 공휴일 인식 date picker
  • 노출 임프레션 → GTM 이벤트

문제정의

기존 프리비아 호텔 검색은 카테고리 트리 중심이었다. 사용자는 도시 → 지역 → 등급 → 가격대를 차례로 좁히며 호텔을 찾아야 했고, "강남역 근처 5분 거리 4성급 호텔"처럼 공간 + 조건이 섞인 의도를 표현할 수 없었다. PC와 모바일 UX가 분리돼 일관성도 부족했다.

사업팀의 요청은 명확했다 — 키워드와 지도 기반 검색으로 전면 개편. 사용자가 자연어 키워드를 던지거나 지도에서 직접 영역을 탐색하면서 호텔을 발견할 수 있도록.

pinch to zoom · drag to pan

목표는 세 가지로 좁혔다.

  1. PC/모바일을 단일 코드베이스로 통합 — 같은 비즈니스 로직, 반응형 레이아웃.
  2. 지도와 리스트의 깊은 통합 — 클러스터링·반경 표시·오버레이뷰·양방향 선택까지.
  3. 분석으로 닫는 루프 — 노출 임프레션 → GTM 이벤트로 사업팀이 사용 패턴을 지속 측정.

구현

4-store Zustand로 도메인 분리

검색 페이지의 상태는 도메인이 명확하게 갈렸다. 각자 다른 라이프사이클이라 하나의 거대 store가 아니라 4개 도메인 store로 분리했다.

pinch to zoom · drag to pan
Store책임
HotelPoiQSStoreQuickSearch — 키워드·날짜·인원 입력 상태
SearchResultStore결과 페이지 — 필터·정렬·페이지·선택 마커
RecentSearchStore사용자 최근 검색어 영속화
(Fallback)입력 없을 때 추천 호텔 캐러셀

이 분리 덕에 검색바를 PC 레이어 모달에 띄우는 케이스와 모바일 바텀시트에 띄우는 케이스가 같은 store, 다른 컴포넌트로 자연스럽게 분기됐다.

PC/모바일을 단일 코드 + 반응형 BLC로

비즈니스 로직 컴포넌트(BLC)를 한 벌만 유지하고 PC/모바일 레이아웃을 위에 얹는 방식. 같은 HotelPoiSearchSection이 PC에서는 레이어 팝업, 모바일에서는 바텀시트로 렌더된다. 마커 클릭 시 동작도 통일 — PC는 맵 중앙으로 panBy, 모바일은 줌 레벨 조정으로 같은 "선택 시 강조" 의미를 살렸다.

Google Maps 깊은 통합 (잔버그의 산)

Google Maps SDK를 클러스터링 · 반경 Circle · InfoWindow 오버레이 · 마커-카드 양방향 선택 · 클러스터 제외 로직 수준까지 다뤘다. 운영하며 누적된 잔버그가 결국 컴포넌트 관심사 분리 리팩토링으로 이어졌다.

사고/요청대응
Circle이 깜박이는 문제, 마커가 겹쳐 보이는 문제마커 렌더링과 Circle 렌더링 관심사 분리
모바일에서 panBy가 부자연스러움모바일에서는 panBy 비활성, 줌만 사용
선택된 마커가 클러스터에 흡수돼 안 보임클러스터링 조건에 줌 레벨 + 선택 상태 반영
view_poi 이벤트의 필드 누락GTM 페이로드 일원화 후 travelInfo 필드 추가
가격이 0원인 호텔 처리필터링하지 않고 리스트 맨 하단으로 정렬 (사업팀 요구)

지도와 리스트 사이의 양방향 인터랙션이 이 화면의 핵심 사용성이다.

pinch to zoom · drag to pan

사용성을 단단히 잡은 작은 훅들

  • useHolidays — 공휴일 API로 캘린더의 휴일을 시각 표시. 검색 의도가 자주 휴가/연휴 중심이라 의사결정에 직결.
  • useItemVisibility — IntersectionObserver로 카드/캐러셀 임프레션 추적. 노출 자체가 사업팀 KPI.
  • usePoiGa — GTM 페이로드 일원화. view_item_list, select_item, search 이벤트를 한 표면에서 발사.

URL Search Params로 공유 가능한 결과 페이지

검색 상태(키워드·날짜·필터·정렬)를 URL에 그대로 반영해 링크 공유로 같은 결과를 재현할 수 있게 했다. 마케팅 캠페인이 검색 결과 페이지로 직접 랜딩하는 시나리오가 가능해졌다.

출시

4월 18일 첫 커밋, 6월 중순에는 검색 핵심 UX·지도·필터·캘린더·인피니트·GTM이 모두 안정화. 사실상 핵심 2개월 안에 출시 가능한 형태로 끝낸 프로젝트다.

배포 후 평가 두 가지:

  • 전사 상반기 배포 서비스 중 가장 신속·정확한 일정 달성 사례로 평가받음.
  • 서비스개발실 호텔매핑 과제의 주요 시작점으로 채택 — 이후 호텔 도메인 통합 작업이 이 코드와 도메인 이해 위에 쌓였다.

배포 이후에도 GTM 이벤트 파라미터 보강·UX 개선(반응형 디테일, 키워드 검색 팝오버 구조, 별 컬러, 캘린더 N박 디자인 등)이 234 커밋 누적까지 이어졌다.

pinch to zoom · drag to pan

결과학습

항목결과
개발 일정핵심 2개월에 출시 가능 상태 도달
전사 평가상반기 배포 서비스 중 신속·정확 #1
후속 확장서비스개발실 호텔매핑 과제의 주요 시작점
코드베이스PC + 모바일 단일 (BLC + 반응형)
누적 변경234 커밋 (배포 후 GTM·UX 보강 포함)

프론트엔드 리드로서 가져간 학습은 네 가지다.

  1. 상태 도메인을 분리하면 PC/모바일 레이아웃 분기가 깔끔해진다. 4-store Zustand가 가장 큰 무기였다 — 같은 store를 PC 레이어 팝업·모바일 바텀시트가 공유하면서 화면만 갈렸다.
  2. Google Maps는 깊이 다룰수록 잔버그가 다발한다. Circle 깜박임·마커 겹침·panBy 부자연 같은 사고는 모두 관심사 분리 리팩토링으로 수렴. SDK 통합은 한번에 안 끝나고 반복 리팩토링이 필요한 영역이다.
  3. 노출 추적은 사업팀과 협업의 통로다. view_item_list / select_item / search 이벤트 페이로드를 사업팀과 정렬하는 과정에서 KPI 자체가 명료해졌다. 분석 이벤트는 기능이 아니라 협업 도구.
  4. 공유 가능한 URL은 마케팅 자산이다. Search Params 전면화는 단순한 라우팅이 아니라 사업팀이 캠페인 링크를 만들 수 있는 인프라가 된다.

Next

  • 호텔매핑 과제로의 도메인 확장 — 이미 시작점으로 평가받았으니, POI ↔ 호텔 메타데이터 매칭의 후속 작업이 자연스러운 다음 단계.
  • AI 큐레이션과의 연결privia-trip 의 자연어 의도 분석 검색이 호텔 POI 검색의 진화 노선. 두 검색 표면을 단일 진입으로 묶을 여지.
  • 임프레션 데이터 기반 랭킹useItemVisibility로 쌓인 노출·클릭 로그를 결과 정렬 가중치로 재투입.