Cllaude99Cllaude99
Syncspot

Syncspot

출발지를 기반으로 최적의 중간 지점을 추천하는 위치 기반 모임 서비스

2024.062025.07·PM 2명 · Design 1명 · FE 2명 · BE 2명
ReactTypeScriptTanStack QueryTailwindCSSVite

Background

팀은 PM 2명, 디자이너 1명, FE 2명, BE 2명 총 7명으로 구성되어 있었습니다. 매주 팀회의를 진행했는데 그때마다 팀원들의 위치가 달라 회의 전 적합한 장소를 찾는 일이 매번 번거로웠습니다. 이 문제의식에서 출발해, 팀원 전원의 위치를 고려한 회의 장소를 추천하는 서비스를 직접 만들기로 했습니다.

TypeScript: JavaScript와 달리 컴파일 단계에서 오류를 확인할 수 있어, 여러 명이 빠르게 기능을 쌓아가는 팀 프로젝트에서 예상치 못한 런타임 오류를 줄일 수 있었습니다.

TanStack Query: 데이터 캐시와 서버 상태를 선언적으로 처리해 중간 지점 계산, 장소 투표처럼 서버 상태에 크게 의존하는 화면의 복잡도를 낮추고자 도입했습니다.

TailwindCSS: FE 팀원 모두 이미 익숙했고, 데모까지 약 1개월밖에 남지 않은 일정 제약 속에서 스타일링 속도를 최우선으로 고려해 선택했습니다.

맡은 업무

  • 서비스의 핵심 기능(중간 지점 찾기, 장소 투표, 시간 투표) 중 중간 지점 찾기, 장소 투표 부분을 맡아 개발
  • 주소 검색 시 디바운스를 적용해 네트워크 요청 횟수를 평균 8.5회에서 1회로 약 87% 줄여 API 호출 최적화
  • 사용자 피드백을 반영해, 마커 추가·삭제 시 지도가 흔들리고 밀리던 현상을 기존 마커를 유지한 채 필요한 경우에만 bounds를 재설정하도록 개선
  • ErrorBoundary와 fallback UI로 예기치 못한 오류가 발생해도 서비스가 중단되지 않도록 처리
  • 반복되는 UI 요소를 공통 컴포넌트로 정리하고 clsx, twMerge, cva로 조건부 스타일과 variant 기반 클래스를 체계적으로 관리
  • GitHub Actions 기반 CI/CD 파이프라인을 구축하고 S3, CloudFront, Route 53으로 정적 배포

Case 01

단일 책임 원칙을 적용한 장소 입력 컴포넌트 개선

700줄 → 400줄

코드량

약 43% 감소

Problem

장소 입력 컴포넌트는 700줄이 넘는 하나의 파일에 UI 렌더링, 데이터 페칭, 상태 관리, 이벤트 핸들링이 모두 뒤섞여 있었습니다. 기획이 조금만 바뀌어도 파일 전체를 다시 파악해야 했고, 단순한 요구사항 하나를 반영하는 데도 복잡한 수정이 뒤따르는 구조적 한계가 있었습니다.

Solution

단일 책임 원칙을 적용해 UI 로직과 데이터 로직을 분리했습니다. 비즈니스 로직은 커스텀 훅으로 분리해 상태 관리와 API 호출 책임을 나누고, Context API로 상태와 함수를 계층 간에 전달하는 구조를 다시 설계했습니다.

Result

관심사 분리와 중복 코드 제거로 700줄이던 파일을 400줄로 줄였습니다. 기능 추가나 버그 수정 시 관련 파일만 수정하면 되는 구조가 되어 유지보수성이 높아졌고, 팀원들이 코드를 이해하는 속도와 요구사항을 구현하는 속도도 함께 빨라졌습니다.

장소 입력 폼

장소 입력 폼

필드 추가 및 유효성 검증

필드 추가 및 유효성 검증

기술적 의사결정

전역 상태 관리 라이브러리를 새로 도입하는 대신, 커스텀 훅 + Context API 조합을 택했습니다. 이 컴포넌트의 상태는 장소 입력 페이지 내부에서만 쓰이는 지역적인 상태였고, 팀의 일정과 러닝 커브를 고려했을 때 이미 익숙한 도구로 책임만 정확히 나누는 편이 리스크가 적다고 판단했습니다.

트레이드오프

커스텀 훅 + Context API로 책임 분리 vs. Redux/Zustand 등 전역 상태 관리 라이브러리 도입

문제의 본질은 "상태 관리 도구의 부재"가 아니라 "책임이 한 파일에 몰려 있는 구조"였기 때문에, 도구를 바꾸기보다 구조를 바꾸는 쪽을 택했습니다.

Case 02

Sentry를 통한 에러 모니터링 시스템 구축

Problem

서비스가 커지면서 실제 사용자 환경에서 어떤 에러가 발생하는지 파악하기 어려웠습니다. 사용자 활동, 네트워크 상태 같은 디버깅에 필요한 맥락 정보를 수집하는 체계가 없었고, API 통신 실패·렌더링 오류·비동기 처리 예외처럼 성격이 다른 에러를 구분해 추적할 방법도 없어 사용자 불만이 접수된 뒤에야 문제를 인지하는 수동적인 구조였습니다.

Solution

에러 유형(API 에러, 일반 예외, 중요 이벤트)별로 캡처 메커니즘을 세분화해 상황에 맞는 컨텍스트 정보를 수집하도록 설계했습니다. SentryErrorBoundary를 도입해 렌더링 오류를 처리하고, 동일한 에러가 중복 보고되지 않도록 플래그 시스템을 함께 구현했습니다. React Query와 통합해 API 요청 실패를 자동으로 캡처하도록 만들어, 백엔드 에러 코드와 연계된 상세 컨텍스트가 함께 기록되게 했습니다.

Result

에러 발생 시 상세한 컨텍스트 정보가 함께 쌓이면서 원인 파악과 문제 해결까지 걸리는 시간이 줄었습니다. "사용자 불만이 들어오면 대응"하던 수동적 방식에서 "이상 신호를 먼저 감지"하는 능동적 모니터링 구조로 전환됐고, 반복적으로 발생하던 주요 에러들의 근본 원인을 해결해 서비스 안정성과 사용자 경험이 함께 개선됐습니다.

상황별 에러 페이지

상황별 에러 페이지

기술적 의사결정

에러를 하나의 통합된 핸들러로 뭉뚱그리지 않고 유형별로 캡처 로직을 나눈 이유는, 에러의 성격마다 필요한 맥락 정보가 달랐기 때문입니다. API 에러는 요청/응답 페이로드가, 렌더링 예외는 컴포넌트 트리 정보가 더 중요했습니다.

트레이드오프

Sentry + SentryErrorBoundary + React Query 통합 vs. 자체 로깅 서버 구축

출시를 앞둔 일정에서 관측 인프라를 직접 구축하는 대신, 검증된 SaaS 도구로 빠르게 커버리지를 확보하는 쪽이 팀 규모와 기간에 더 맞는 선택이었습니다.

Case 03

모바일 사용자 경험을 위한 드래그 가능한 바텀시트 구현

43% → 89%

모바일 만족도

1·2차 사용자 테스트 기준

Problem

모바일 사용자 비율이 늘면서, 제한된 화면 공간 안에서 장소 입력과 중간 지점 결과를 지도와 함께 동시에 보여줘야 하는 화면 구성 제약이 발생했습니다.

Solution

사용자가 필요에 따라 정보 노출량을 직접 조절할 수 있도록 맨 아래에서 위로 끌어올릴 수 있는 패널 UI를 구현했습니다. 다양한 화면 크기와 방향 변경에 대응하기 위해 dvh 단위 기반으로 높이를 동적으로 계산했고, 드래그 로직은 useBottomSheet라는 커스텀 훅으로 분리해 UI와 인터랙션 로직의 결합도를 낮췄습니다. 터치와 마우스 이벤트를 모두 지원해 모바일과 태블릿 등 다양한 입력 환경에서 일관된 경험을 제공했습니다.

Result

1차·2차 사용자 테스트 결과, 바텀시트 도입 이전 43%였던 모바일 환경 사용자 만족도가 89%로 올랐습니다. 지도와 상세 정보를 동시에 보여주면서도 사용자가 필요에 따라 정보량을 조절할 수 있는 직관적인 인터페이스를 제공했고, 이 컴포넌트는 장소 입력·중간지점 결과 페이지 등 여러 핵심 화면에서 재사용되어 개발 효율에도 기여했습니다.

장소 추천 및 결과 화면

장소 추천 및 결과 화면

기술적 의사결정

서드파티 바텀시트 라이브러리 대신 직접 구현을 택한 이유는, 서비스 특성상 지도·마커·결과 카드가 함께 보여야 하는 레이아웃 요구가 있어 범용 라이브러리로는 세밀한 제어가 어려울 것으로 판단했기 때문입니다. dvh 단위를 채택한 것도 모바일 브라우저 주소창 노출/숨김에 따라 뷰포트 높이가 흔들리는 문제를 근본적으로 해결하기 위해서였습니다.

트레이드오프

useBottomSheet 커스텀 훅으로 직접 구현 vs. 서드파티 바텀시트 라이브러리 도입

지도·카드가 함께 배치되는 레이아웃에 맞춰 드래그 임계값과 높이 계산을 세밀하게 조정할 필요가 있어, 제어권을 완전히 갖는 쪽을 택했습니다.

회고

잘한 점

장소 입력 컴포넌트를 SRP로 리팩터링한 경험을 계기로, 이후 새 기능을 추가할 때부터 처음부터 책임을 나눠 설계하는 습관이 생겼습니다. 덕분에 바텀시트처럼 여러 화면에서 재사용되는 컴포넌트를 만들 때도 결합도를 낮게 유지할 수 있었습니다.

아쉬운 점

에러 모니터링 체계를 서비스 중반에야 도입하면서, 초기 출시 구간에서 발생했을 수 있는 에러들은 컨텍스트 없이 지나쳤을 가능성이 있습니다. 관측 가능성은 기능 개발만큼이나 초기부터 함께 설계했어야 할 영역이라는 것을 배웠습니다.

다시 만든다면

지도, 장소 입력, 바텀시트가 서로 얽혀 있는 화면 구조상 상태가 흩어지기 쉬웠는데, 프로젝트 초반에 이 화면들의 상태 소유권을 더 명확히 설계했다면 중간의 리팩터링 비용을 줄일 수 있었을 것 같습니다.