이야기를 걷다
드라마·영화·설화의 배경이 된 실제 장소를 이야기의 흐름 그대로 이어 걷는 서사 여행 가이드입니다. Flutter 앱과 이를 받치는 API 서버·운영 백오피스까지 개발 전 구간을 혼자 맡아 Google Play에 출시했습니다.
프로젝트 소개 및 개발 배경
기존 관광 서비스는 지역을 출발점으로 두고 촬영지를 '점'으로 나열합니다. 「이야기를 걷다」는 반대로 작품을 출발점으로 삼습니다. 인물의 감정선과 사건의 순서가 루트를 결정하고, 사용자는 장면이 태어난 공간을 '선'으로 이어 걷습니다. 사진 한 장 찍고 떠나는 여행(Snap & Go)이 아니라, 머물며 이야기를 느끼는 여행(Stay & Feel)을 지향합니다.
한국관광공사 TourAPI를 활용하는 2026 관광데이터 활용 공모전(웹·앱 개발 부문) 출품을 계기로 시작했습니다. 첫 무대는 경북 포항과 경주입니다. 구룡포 골목에 남은 드라마 속 그 자리부터 천년 고도에 전해지는 신라 설화의 현장까지, 15개 작품 · 55개 장소를 이야기 순서대로 엮었습니다.
기획·디자인·콘텐츠는 PM이, 개발은 제가 맡았습니다. 2026년 7월부터는 사용자가 만나는 Flutter 앱뿐 아니라 앱이 호출하는 API 서버(NestJS on Cloudflare Workers), 콘텐츠를 입력·검수하는 운영 백오피스(React), 데이터베이스와 배포 인프라, Google Play 출시와 릴리스 대응까지 개발 전 구간을 1인으로 구현·배포·운영하고 있습니다.
기술 스택 상세 설명
Flutter + Feature-First Clean Architecture: 기능(feature)마다 domain / data / presentation 3계층으로 나누고, 공통 인프라(network·storage·theme·map·observability)는 core에 모았습니다. 테스트 289개가 모두 통과하는 상태를 유지합니다.
Riverpod 3 + 코드 생성: 상태 관리는 Riverpod 3(riverpod_generator)으로, API 모델은 json_serializable로 생성합니다.
백엔드 OpenAPI 스냅샷을 기준으로 모델을 동기화해 앱·서버 계약이 어긋나지 않게 했습니다.
견고한 네트워크 계층: Dio 인터셉터에서 인증 헤더, 408·429·5xx에 대한 지수 백오프 재시도, 오류 변환을 일괄 처리합니다. 실패해도 빈 화면 대신 대체 표시(fallback)를 보여주는 것이 원칙입니다.
소셜 로그인: 구글(Firebase Auth)·네이버 네이티브 SDK를 지원합니다. 서버는 JWT 대신 불투명 세션 토큰을 발급하고 요청마다 DB로 검증합니다. 회원가입 없이도 모든 이야기를 둘러볼 수 있고, 체크인·저장 시점에만 로그인을 요청합니다.
지도: 네이버 지도 SDK로 루트·통합 지도를 그리고, 길찾기는 네이버 지도·카카오맵·구글 지도 중 설치된 앱을 골라 연결합니다.
개인정보 최소화: 위치 권한을 요청하지 않습니다(매니페스트 권한은 INTERNET 하나). 관측은 Firebase Crashlytics·Analytics로 합니다.
공모전 규정 준수 설계: TourAPI의 사진·개요 같은 콘텐츠성 데이터는 저장하지 않고 서버가 실시간 프록시합니다. 앱은 메모리에만 10분간 캐시하고 디스크에는 남기지 않습니다.
API 서버 · 운영 백오피스: API 서버는 NestJS 11을 Cloudflare Workers에 올리고 Hyperdrive로 Supabase Postgres에 연결했습니다(Prisma 7). 백오피스는 React 19 + Vite SPA로, 작품·장소·서사 입력과 TourAPI 검색, 공개 최소 조건 검수를 제공하며 Cloudflare Access와 서버 측 JWT 재검증으로 이중 보호합니다.
주요 기능
- 작품 고르기: 포항·경주를 배경으로 한 드라마·영화·설화·역사 컬렉션을 매거진형 카드로 보여주고, 에디터 픽을 강조합니다.
- 서사 루트: 이야기 순서대로 이어지는 장소를 지도 위 순번 마커와 바텀시트 목록으로 한눈에 보여줍니다.
- 장소 서사 카드: 그곳에 깃든 장면과 정서를 매거진처럼 읽고, 공식 영상(YouTube)의 명장면을 카드 안에서 바로 재생합니다. 반려동물 동반·무장애 정보 배지도 표시합니다.
- 길찾기: 네이버 지도·카카오맵·구글 지도 중 원하는 앱으로 바로 연결합니다.
- 통합 지도: 포항·경주 전체 장소를 지역별 색 마커로 표시하고, 밀집 구간은 클러스터링으로 묶습니다.
- 방문 체크인 & My Map: 다녀온 장소를 기록해 루트 완주에 도전하고, 저장한 코스와 걸어온 발자취를 한 장의 지도로 모아 봅니다.
개발 과정의 도전과 해결
릴리스 빌드에서만 실패한 구글 로그인. 디버그 빌드는 정상인데 Play에서 받은 앱만 구글 로그인이 실패했습니다. 원인은 Play App Signing이 재서명한 앱 서명 키의 SHA-1이 Firebase에 등록되지 않은 것이었습니다. 키 지문 6건을 대조해 등록하고 설정 파일을 재생성해, 앱 코드 수정 없이 해결했습니다.
R8 난독화가 깨뜨린 네이버 로그인. 릴리스 빌드에서 네이버 로그인이 ClassCastException으로 죽고,
서버에는 요청이 한 건도 오지 않았습니다. R8이 네이버 SDK 내부 Retrofit의 제네릭 시그니처를 제거한 것이 원인이었습니다.
ProGuard keep 규칙을 추가해 실기기에서 로그인 성공을 확인했습니다.
바텀시트 무한 재진입 크래시. 루트 화면의 장소 목록 바텀시트에서 StackOverflowError가 발생했습니다.
스크롤 종료 콜백이 자기 자신을 다시 호출하는 재진입 루프였고, 재진입 가드를 넣은 뒤
위젯 테스트로 실패→성공을 재현해 회귀를 막았습니다.
처음 여는 사용자에게만 안 뜨는 주소 정보(콜드 스타트). 서버가 막 깨어난 직후 첫 TourAPI 호출이 4.8초 걸려 4초 타임아웃에 걸리고 있었습니다. 실측값을 근거로 타임아웃을 조정하고, 배포 후 같은 장소가 정상 응답하는 것을 확인했습니다. 앱·서버를 모두 직접 다루기에 증상(앱)과 원인(서버)을 한 번에 추적할 수 있었습니다.
향후 계획
- 콘텐츠 확장: 포항·경주 외 지역과 작품을 계속 추가할 예정입니다.