🏊
공공데이터에 없는 정보라서, 데이터를 직접 모으고 검증하는 일부터가 개발이었습니다.
배경
"오늘 이 시간에 자유수영을 할 수 있는 수영장이 어디지?"라는 생활 속 질문에서 시작한 개인 프로젝트입니다. 권역별 수영장의 자유수영 가능 여부·요금·위치를 한 화면에서 보여주는 정보앱 MVP를 기획부터 배포까지 혼자 만들었습니다.
한 일
- 데이터 수급: 크롤링으로 직접 확보: 자유수영 시간표와 요금은 공공데이터에 없습니다. 지역 도시공사 홈페이지를 크롤링해 4개 시설의 자유수영 37개 세션과 강습 18개를 정규화했습니다.
- 데이터 검증·정직성: 네이버 플레이스와 교차 검증해 시설명 오류를 바로잡고, 좌표는 하드코딩해 런타임 지오코딩 의존을 없앴습니다. 화면에는 "확인 날짜"를 표기하고, 검증 못 한 값은 지어내는 대신 "시설 확인"으로 명시했습니다.
- 스코프 판단도 조사로: 사설 수영장 10곳을 전수 조사해 자유수영을 제공하지 않는 것을 확인하고, 근거를 남긴 뒤 기능 범위에서 제외했습니다.
- 디자인 시스템과 로직을 같은 언어로: Figma로 디자인 토큰(컬러·상태색)을 먼저 세우고, 영업 상태색(now-open / upcoming / closed)을 상태 계산 함수의 반환값과 1:1로 매핑했습니다. 디자인과 로직이 같은 단어를 쓰니 화면과 데이터가 어긋날 틈이 줄어듭니다.
- 죽지 않는 앱 + 자동화: 지도 키가 없거나 도메인 미등록(401)이어도 앱이 죽지 않도록 권역 핀 폴백을 넣었고, Next.js not-found의 서버 redirect가 동작하지 않는 것을 실측으로 확인해 catch-all 라우트 307 redirect로 우회했습니다. GitHub Actions로 lint → typecheck → build 게이트를 걸고 Vercel 자동 배포 + PR 프리뷰, PWA 구성.
- 백엔드는 필요해진 시점에 증설: MVP는 백엔드 없이 정적 JSON으로 검증하고, 백엔드가 있어야만 가능한 기능(오류 제보 수집, 내 제보 내역, 아침 요약 푸시)이 생긴 시점에 NestJS + Prisma + PostgreSQL을 증설했습니다. 로그인 대신 익명 기기 ID로 제보를 묶어 가입 마찰을 없애고, 프론트는 API가 죽어도 정적 데이터로 동작하는 폴백을 유지합니다.
- 무료 인프라 직접 운영: Oracle Cloud 무료 ARM 서버에 Docker Compose(Caddy HTTPS 자동 발급 + API + DB)로 배포했습니다. 실수 과금을 막으려고 쿼터 잠금(무료 사양 외 생성 차단)과 예산 알림을 걸어 월 0원으로 운영합니다.
- 알림은 서버가 계산해서 보낸다: 매일 오전 8시 크론이 오늘의 자유수영 세션을 계산해 Web Push(VAPID)로 발송합니다. 만료된 구독은 발송 시 자동 정리되고, 요약 로직은 단위테스트로 검증했습니다.
결과
- 4개 시설: 자유수영 37세션 + 강습 18개 정규화
- 1인: 기획 → 데이터 → 개발 → 배포 풀사이클
- CI/CD: GHA 게이트 + Vercel 자동 배포·PR 프리뷰
- 백엔드 증설: 제보 수집·내 제보 내역·아침 요약 Web Push (NestJS + PostgreSQL, 리포)
- 월 0원 인프라: Oracle Cloud ARM + Docker Compose 직접 운영 (HTTPS 자동 발급, 쿼터 잠금·예산 알림으로 과금 방어)
정리
화면보다 데이터의 정확성이 제품 신뢰를 만든다는 걸 확인한 프로젝트입니다. 없는 값을 그럴듯하게 채우지 않고 "확인 못 함"을 그대로 보여주는 정직성 원칙을, 개인 프로젝트에서도 지키려고 했습니다. 그리고 처음부터 풀스택으로 짓는 대신 정적 MVP로 먼저 검증하고 필요가 생긴 시점에 백엔드를 증설하면서, 제품의 생애주기를 기획부터 서버 운영까지 혼자 끝까지 겪어본 프로젝트가 됐습니다.