2026.01 — 2026.09회사 프로젝트

Vgolf

골프장 경기관제 — GPS 카트 추적 & 실시간 라운드 관리

맡은 일
Frontend Developer (경기관제 태블릿 · 미디어바) · 경량화 단독 진행
기간
2026.01 — 2026.09
소속
플럭시티클라우드팀

골프장 실시간 라운드 관리 플랫폼의 경기관제 앱입니다. GPS 기반 카트 추적, Proj4 좌표 변환(위경도→코스맵 픽셀), 지오펜싱 자동 홀 전환, 움직이는 카트 위에서의 터치 UX처럼 골프장이라는 환경에만 있는 문제들을 풀었습니다.

지금도 현장에서 매일 돌아가는 서비스라 안정성과 마감에 특히 신경 썼습니다. 미디어바(카트에 붙는 보조 화면)도 같은 화면을 그대로 띄우기 때문에 함께 봐야 했습니다.

2026년 9월에는 한 달 남짓 이 앱을 통째로 다시 재고 줄였습니다. 모바일 코드를 분리해 내보내고, 폰트 여섯 종 열다섯 face를 Pretendard 가변 하나로 모으고, 화면별 지연 로딩과 이미지 압축을 붙였습니다.

상태 저장은 Recoil에서 zustand로 옮기면서 저장 동작을 테스트 40개로 먼저 고정해 두고 바꿨습니다. 5MB(−88%)가 됐고, 첫 화면 전에 읽는 JS·CSS가 1/3 수준으로 줄었습니다.

같은 플랫폼의 F&B 주문·사이니지는 별도 프로젝트(VGOLF F&B)로 정리했습니다.

  1. i.Problem

    현장 태블릿은 소스를 업데이트할 때마다 72.9MB 를 내려받았습니다. 느린 회선에서는 1분이 넘고, 카트에 붙는 미디어바는 블루투스로 4분 가까이 받았습니다. 상태는 값 하나가 바뀔 때마다 전체를 직렬화하고 있었습니다.

  2. ii.Action

    모바일 코드를 분리해 내보내고, 폰트 6종 15 face 를 Pretendard 가변 하나로 모았습니다. 쓰지 않는 파일과 CSS · 번역 · 패키지를 전수로 걷어내고 화면별 지연 로딩을 붙였습니다. 상태는 zustand 로 옮기되, 옮기기 전에 Recoil 시절 저장 동작을 테스트 40개로 먼저 고정했습니다.

  3. iii.Result

    받는 파일이 8.5MB 가 되어 10Mbps 에서 61초 걸리던 것이 7초가 됐습니다. 첫 화면 전에 읽는 JS 는 2,048KB 에서 738KB 로 줄었습니다. 옛 버전과 새 버전을 같은 데이터로 띄워 8개 화면을 줄 단위로 비교했고, 달라진 것은 시계 글자뿐이었습니다.

Pl. 1현재 홀. GPS로 잡은 카트 위치에서 홀컵까지 남은 거리와 고저차를 코스 맵 위에 올립니다. 오른쪽은 티샷 순서를 정하는 패널.

핵심

  1. 01

    태블릿이 업데이트 때 받는 파일 72.9MB → 8.5MB (−88%)

    10Mbps 회선에서 61초 걸리던 것이 7초

  2. 02

    미디어바 블루투스 첫 전송 46.0MB·3분 56초 → 6.8MB·약 35초

  3. 03

    첫 화면 전에 읽는 JS 2,048KB → 738KB, CSS 780KB → 339KB

  4. 04

    Recoil → zustand 전환

    저장 동작을 테스트 40개로 먼저 고정한 뒤 141개 파일을 옮겼습니다

나머지 4개 더 보기
  1. 05

    지금도 현장에서 매일 돌아가는 서비스

    안정성과 마감에 주력

  2. 06

    GPS 카트 추적

    500ms 주기 GPS 읽기, Proj4 좌표 변환(위경도→코스맵 픽셀), 지오펜싱 자동 홀 전환

  3. 07

    움직이는 카트 위 터치 UX

    Ghost Click 방지, useTap(30px 흔들림 허용), 더블클릭 가드

  4. 08

    네트워크 탄력성

    연속 오류 감지 시 폴링 일시정지, React Query 캐싱, Teams Webhook 텔레메트리

화면

좌우로 넘겨 보실 수 있습니다

Pl. 2of10

Pl. 2홀 상세 — 그린 경사를 색으로 보여주고, 코스에서 주의할 점을 안내합니다. 오른쪽에서 홀을 바로 골라 넘어갈 수 있습니다.
Pl. 3스코어 카드. 전반·후반을 나눠 홀별 타수와 합계를 보여주고, 버디 이하는 하트로 표시합니다.
Pl. 4전체 홀 경기 현황. 18홀에 흩어진 팀의 위치와 대기 팀을 한 화면에서 봅니다. 관제실에서 종일 띄워 두는 화면입니다.
Pl. 5그날 예약 목록. 팀마다 카트 번호와 전·후반 코스를 지정하고 라운드를 시작합니다.
Pl. 6라운드 시작 설정 — 캐디 유무, 부(部), 전·후반 코스, 티오프 시각을 고르고 플레이어를 채웁니다. 빠진 값이 있으면 시작 버튼이 잠깁니다.
Pl. 7캐디노트. 고객별 라운드 안내 동의와 클럽 장비 확인 상태를 체크하고, 관제·식음에서 온 메시지와 메모를 같이 봅니다.
Pl. 8식음 주문 — 그늘집·레스토랑·스타트하우스를 골라 담습니다. 품절은 주문할 수 없게 막고, 포장 여부를 항목마다 고릅니다.
Pl. 9주문 내역. 요청·취소·조리중 같은 상태가 매장에서 바뀌는 대로 따라 움직입니다.
Pl. 10라운드 중 특정 지점에 도달하면 뜨는 프로모션 안내.

실제 운영 화면입니다. 내장객·캐디 이름은 알아볼 수 없게 처리했습니다.

자세히

궁금한 항목을 펼치면 그때 무엇이 문제였고 어떻게 판단했는지 나옵니다.

모바일 코드 분리

태블릿·미디어바만 남기고 모바일 화면(라우트 12 · 모듈 55 · 전용 CSS 22 등)을 별도 저장소로 내보냈습니다. 남는 쪽의 기능과 화면은 한 줄도 달라지지 않는 것을 원칙으로 잡았습니다

폰트 6종 15 face(29.1MB, 28개 파일) → Pretendard 가변 woff2 하나(2.0MB)

화면에 보이던 굵기는 그대로 유지

쓰지 않는 정적 파일 102개 제거

public 은 참조 여부와 상관없이 복사되므로 코드가 아니라 파일 기준으로 전수 확인했습니다

이미지 이중 복사 제거(zip 36.6 → 26.3MB), 큰 이미지 21개 무손실 WebP, 배경 6장만 품질 95 손실 압축(13.1 → 2.2MB)

화면별 지연 로딩 + 부팅 후 미리 받기

첫 화면에 필요한 것만 먼저 읽습니다

전역 CSS 에서 어떤 화면에도 매칭될 수 없는 규칙 제거 (layout.css 18,056 → 13,470줄)

쓰지 않는 번역 키 116개·패키지 5개 제거, 런타임 의존성 27 → 21

상대 경로 import 1,216곳을 @/ 로 정리

먼저 Recoil 시절의 저장 동작을 테스트 40개로 고정했습니다

무엇을 저장하고 무엇을 저장하지 않는지, 같은 값을 넣었을 때 어떻게 되는지, reset 하면 저장소에서 어떻게 빠지는지까지

atom 103개를 hot·session·bulk·config·volatile 다섯 버킷으로 나누고, 1초 지연 쓰기와 바뀐 키만 쓰기로 바꿨습니다

현장 태블릿의 저장 데이터를 잃지 않도록 recoil-persist 값을 한 번 옮기는 경로를 두고, 옛 버전으로 실제 값을 만든 뒤 새 버전을 띄워 17개 키가 값 해시까지 같은지 확인했습니다

미디어바는 본 화면과 영역 iframe 이 같은 저장소를 쓰는데, 버킷을 통째로 저장하면 한 쪽이 다른 쪽 값을 덮었습니다

바뀐 키만 합쳐 쓰도록 고쳤습니다

옛 버전과 새 버전을 같은 데이터로 띄워 8개 화면의 모든 요소 computed style 을 줄 단위로 비교

시계 글자와 그 폭만 달랐습니다

테스트가 실제로 잡는지 보려고 일부러 코드를 망가뜨려 보고, 안 잡히는 구간에는 테스트를 더 넣었습니다

배운 것

끝난 뒤에 남은 것들을 적었습니다.

매일 현장에서 도는 앱을 유지보수하면서, 기능을 더하는 것보다 '아무것도 깨지지 않게' 바꾸는 일이 더 어렵다는 걸 배웠습니다. 그래서 경량화를 할 때도 남는 쪽의 기능과 화면이 한 줄도 달라지지 않는 것을 원칙으로 두고, 옛 버전과 새 버전을 같은 데이터로 띄워 화면을 줄 단위로 비교했습니다.

바꾸기 전에 지금 동작을 먼저 테스트로 고정하는 습관이 여기서 생겼습니다. Recoil 시절 저장 동작을 테스트 40개로 먼저 묶어 두었기 때문에, 141개 파일을 옮기면서도 무엇이 달라졌는지를 감이 아니라 결과로 확인할 수 있었습니다.

움직이는 카트 위에서 쓰는 화면은 사무실 화면과 다른 기준이 필요했습니다. 손이 흔들리는 탭, 끊기는 회선, 블루투스로 4분 가까이 걸리던 업데이트처럼 현장에서만 보이는 문제를 하나씩 겪으면서, 사용 환경을 먼저 그려 보고 설계하게 됐습니다.

반년 동안 이 앱을 지키며 쌓은 이해와 신뢰가 다음 단계인 PM30 개편을 맡는 바탕이 됐습니다. 유지보수가 지루한 뒷정리가 아니라, 프로젝트를 가장 깊이 알게 되는 자리라는 걸 이때 알았습니다.

쓴 기술

ReactTypeScriptViteZustandReact QueryVitestSCSSMUIi18nProj4WebViewDockerNginx