운전자의 사용 맥락을 고려해 설계된 위치 기반 휴게소 큐레이션 서비스입니다. 허위 리뷰 방지를 위해 OCR 영수증 인증 시스템을 구축하여 데이터 신뢰도를 높였고, 실시간 지오펜싱(Geofencing) 과 FCM을 결합해 주행 중에도 끊김 없는 정보 푸시 알림을 제공합니다. 사용자의 이동 동선을 고려한 인터셉팅 라우트 등 고도화된 UX 설계에 집중했습니다.
🛠 사용 기술
분류
기술 스택
핵심 역할
Framework
Next.js 14 (App Router)
Route Groups 및 Parallel/Intercepting Routes 기반 구조 설계
Verification
Clova OCR API
영수증 이미지 분석 및 지점명/주소 데이터 검증 로직 구현
Push Service
FCM (Firebase Messaging)
Service Worker 기반 백그라운드 푸시 알림 파이프라인 구축
PWA
Next-PWA / Workbox
오프라인 캐싱 및 홈 화면 추가(A2HS) 등 네이티브 경험 제공
State / Map
Redux Toolkit, Kakao Map
전역 상태 관리 및 인터랙티브 지도 핸들링
Next.js, FCM, Clova OCR이 유기적으로 맞물리는 서비스 아키텍처 구성도
2. ✨ 핵심 기능
"기술로 구현한 데이터의 신뢰성과 앱 이상의 경험"
OCR 기반 방문자 인증 시스템 (The Kick): 영수증 촬영 시 Clova OCR이 상호명과 주소를 추출합니다. 이를 현재 위치한 휴게소 정보와 실시간 대조하여, 검증된 사용자에게만 리뷰 작성 권한을 부여하는 신뢰 프로세스를 구축했습니다.
FCM 실시간 위치 푸시 알림: 유저의 이동 경로를 감지하여, 백그라운드 상태에서도 실시간 알림을 보냅니다. useFCM 커스텀 훅과 서비스 워커를 통해 웹 환경의 알림 제약을 극복했습니다.
인터셉팅 라우트 기반 로드뷰: Next.js의 @modal 기능을 활용해 페이지 이동 없이 휴게소 파노라마 뷰를 띄웁니다. 새로고침 시에도 컨텍스트가 유지되는 웹의 장점과 앱의 부드러운 모달 UX를 결합했습니다.
사용자 소비 패턴 시각화: account 페이지에서 Chart.js를 활용해 지출 내역을 카테고리별로 시각화합니다. 단순 검색을 넘어 개인화된 대시보드 기능을 제공합니다.
상황 맞춤형 실시간 큐레이션: 현재 위치의 기상 상황(OpenWeatherMap 연동)에 맞춰 최적화된 휴게소 메뉴와 편의시설을 우선 노출하는 동적 렌더링 로직을 구현했습니다.
보안 및 효율성: 브라우저에서 직접 Spring Boot 서버에 접근하지 않고, Next.js Server Action을 중간 프록시(BFF)로 활용했습니다. 이를 통해 API 키 노출을 방지하고, 초기 구동 시 전국 휴게소 좌표 데이터를 안전하게 프리패칭하여 클라이언트 메모리에 로드했습니다.
서버 부하 분산: 대규모 데이터 연산을 서버가 아닌 클라이언트 자원을 활용하도록 설계하여, 동시 접속자가 늘어나도 서버의 연산 부하가 증가하지 않는 구조를 지향했습니다.
수학적 설계: 지구가 구형임을 고려한 하버사인 공식을 통해, 좌표 데이터만으로 두 지점 사이의 최단 직선 거리를 정확하게 산출했습니다.
단위 동적 렌더링: 산출된 거리에 따라 1km 이상은 km, 미만은 m 단위로 자동 변환하여 주행 중인 사용자의 시각적 인지 속도를 개선했습니다.
TYPESCRIPT
/** * 하버사인 공식을 활용한 위경도 기반 거리 계산 로직 * @description 1km 이내 진입 시 m 단위로 변환하여 실시간 거리 정보 제공 */export const getDifferDistance = (lat1: number, lon1: number, lat2: number, lon2: number) => { const
/** * 알림 클릭 시 기존 탭을 추적하여 포커싱하거나 새 창을 여는 UX 최적화 로직 * @description 불필요한 리소스 낭비를 방지하고 사용자의 탐색 맥락을 유지함 */self.addEventListener('notificationclick', function (event) { event.notification.close(); const urlToOpen = event.notification.data.click_action
🔍 OCR 데이터 검증 (Clova OCR)
"신뢰할 수 있는 리뷰 생태계를 위한 데이터 무결성 검증"
단순한 텍스트 추출을 넘어, 영수증 데이터가 실제 서비스 내 휴게소 정보와 일치하는지 검증하는 교차 확인(Cross-Check) 알고리즘을 구현하여 리뷰의 신뢰도를 높였습니다.
/** * OCR 추출 데이터와 DB 정보의 정밀 대조를 통한 권한 부여 * @description 단순 일치가 아닌 공백 제거 및 포함 관계 분석을 통한 정합성 확보 */const isVerified = (ocrData: OCRResponse, restAreaData: RestAreaData): boolean => { // 1. 상호명 검증: 공백 제거 후 포함 여부 확인 (예: '안성 휴게소' vs '안성휴게소') const normalize = (str:
4. 🎨 사용자 경험 전략
"사용자의 맥락(Context)을 이해하고 웹과 앱의 경계를 허무는 인터페이스 설계"
고속도로 주행 중이라는 특수한 사용 환경을 고려하여, 최소한의 시선 분산으로 최대한의 정보를 직관적으로 획득할 수 있는 UX 아키텍처를 구축했습니다.
📱 모바일 퍼스트 & 엄지 존(Thumb-zone) 최적화
하단 지향 인터랙션: 주행 중 조작의 위험성과 한 손 조작 편의성을 고려하여, 주요 메뉴와 상세 카드 UI를 하단에 배치하는 'Bottom-Heavy' 디자인을 채택했습니다.
스와이프 기반 카드 UI: 복잡한 버튼 클릭 대신 직관적인 스와이프 인터랙션을 통해 여러 휴게소 정보를 빠르고 피로도 낮게 탐색할 수 있도록 설계했습니다.
💉 Next.js 고도화 라우팅: 맥락 유지
단순한 페이지 이동을 넘어, 사용자의 탐색 흐름이 끊기지 않는 상태 중심의 라우팅을 구현했습니다.
Intercepting Routes (@modal): 메인 지도를 탐색하던 맥락을 유지하면서 특정 휴게소 정보를 가로채기 라우팅으로 처리했습니다. 이를 통해 사용자는 상세 정보 확인 후 '뒤로 가기' 시 별도의 로딩 없이 즉각적으로 이전 지도 화면으로 복귀할 수 있습니다.
Parallel Routes: 지도 화면과 대시보드 요소를 병렬로 구성하여, 독립적인 로딩 상태(Skeleton UI)를 관리하고 화면 전체의 깜빡임 없이 필요한 데이터만 부드럽게 업데이트되도록 처리했습니다.
지도를 배경으로 유지한 채 상세 정보를 노출하는 인터셉팅 라우트 구조
🎬 프레임 단위의 네이티브 감성
선언적 레이아웃 전이(Layout Transitions): Framer Motion의 layout 속성을 활용하여, 리스트가 확장되거나 모달이 등장할 때 발생하는 UI 변경 사항을 끊김 없는 애니메이션으로 연결했습니다. 이는 사용자가 현재 어느 맥락에 있는지 직관적으로 인지하도록 돕습니다.
물리 법칙 기반의 반응성: 고정된 수치의 이징(Easing) 함수 대신, Spring Physics를 적용하여 유저 인터랙션에 즉각적이고 생동감 있는 반응을 제공함으로써 네이티브 앱 특유의 탄성 있는 터치감을 재현했습니다.
🚀 네이티브 앱 경험의 구현 (PWA Integration)
"웹의 접근성과 앱의 몰입감을 결합한 심리스(Seamless) UX"
Standalone 모드 적용: 브라우저의 상단 URL 바 등 불필요한 컨트롤러를 제거하여, 독립된 애플리케이션으로서의 완성된 몰입감을 제공했습니다.
A2HS(Add to Home Screen) 기반 접근성: 홈 화면 아이콘을 통해 즉각 진입할 수 있는 환경을 구축하여 사용자의 접근 편의성과 서비스 리텐션을 확보했습니다.
일관된 브랜드 온보딩 경험: 전용 스플래시 스크린과 테마 컬러 설정을 통해 앱 실행 시점부터 일관된 브랜드 아이덴티티를 전달했습니다.
URL 바가 제거된 Standalone 모드 구동 및 네이티브 감성의 온보딩 플로우
5. 📝 회고
"구현의 즐거움에서 시작해, 설계의 중요성을 깨닫기까지"
2년 전, 이 프로젝트를 시작할 당시 저는 네트워크 지식이나 프레임워크의 깊은 구조를 다 알지 못했습니다. 하지만 "내가 만든 기능이 누군가에게 실제 가치를 줄 수 있다" 는 사실에 몰입해 밤을 지새웠던 기억이 납니다.
기술적 도전과 성취: 리액트 외에 Next.js나 SSR(Server Side Rendering) 같은 낯선 환경을 처음 접하며 수많은 시행착오를 겪었습니다. 하지만 기능을 구현하는 재미에 몰입하여 끝까지 포기하지 않았고, 그 결과 공모전 1등 수상이라는 값진 결과를 얻었습니다.
프론트엔드 엔지니어로서의 역할: 화면을 그리는 것 이상으로, OCR 검증이나 FCM 토큰 관리처럼 서비스의 신뢰도와 직결되는 로직을 고민하며 프론트엔드가 비즈니스 로직에 기여할 수 있는 범위를 확장했습니다.
사용자 중심의 설계: '고속도로 위 운전자'라는 구체적인 상황을 설정하고, 가장 적은 인터랙션으로 필요한 정보에 도달하게 만드는 과정을 통해 사용자 경험(UX)에 기반한 개발 감각을 익혔습니다.
지금 다시 이 프로젝트를 복기해 보니 기술적으로는 아쉬운 부분도 보이지만, 그 시절 가장 뜨겁게 최선을 다했던 제 모습이 담겨 있어 감회가 새롭습니다. 이때의 치열했던 고민들은 현재 제가 더 복잡한 문제를 해결하고 구조적인 코드를 작성하는 데 있어 큰 밑거름이 되었습니다.