cmod.ify

[Troubleshooting] Wattup #2 프론트엔드 트러블 슈팅 본문

Project

[Troubleshooting] Wattup #2 프론트엔드 트러블 슈팅

modifyC 2026. 3. 10. 23:54
728x90
반응형
TROUBLESHOOTING LOG

WattUp 프론트엔드 개발기

고민했던 것들, 막혔던 것들, 그리고 해결한 것들

Map Marker
📍
Troubleshooting 01
개별 충전소 마커 — offset 불일치 문제
🔴 문제

원래는 마커를 원 두 개(외곽 원 + 내부 원)로 구성했는데, 네이버 맵 위에 렌더링하면 두 원의 중심이 맞지 않았다. offset 값을 아무리 조정해도 줌 레벨마다 어긋났다.

🟡 원인

원 두 개를 독립된 DOM 요소로 쌓는 구조라 각각의 기준점(anchor)이 달라서 생긴 문제였다. 줌 레벨이 바뀔 때마다 픽셀 단위 계산이 틀어지니 고정값으로는 해결이 안 됐다.

🟢 해결

원 두 개를 버리고 하나의 원 + CSS 애니메이션(ripple)으로 구조를 바꿨다. anchor를 단일 중심점으로 잡으니 offset 문제가 사라졌고, ripple 효과 덕분에 시각적으로도 더 자연스러워졌다.

💡 깨달은 것 DOM 구조가 복잡할수록 좌표 계산 오차가 커진다. 단순하게 만드는 것 자체가 해결책이 될 수 있다.
🗺️
Troubleshooting 02
클러스터링 마커 — 데이터 과부하 문제
🔴 문제

목 데이터로 개발할 때는 괜찮았는데, 백엔드와 연결하자마자 서울 충전소 데이터가 한 번에 다 날아왔다. 지도가 버벅이고 마커가 수백 개씩 한꺼번에 그려지면서 사실상 사용이 불가능한 상태가 됐다.

🟡 원인

목 데이터로 개발할 때 데이터 양이 적어서 성능 문제를 미리 발견하지 못했다. 실제 공공데이터는 규모가 달랐다.

🟢 해결

구(區) 단위로 쪼개서 조회하는 방식으로 변경했다. 드롭다운에서 구를 선택하면 그 구의 충전소만 불러오도록 API 호출 방식을 바꿨다.

💡 깨달은 것 목 데이터 개발은 빠르지만 실제 데이터 규모를 미리 고려해야 한다. 성능 이슈는 실제 데이터를 붙여봐야 보인다.
API & React
Troubleshooting 03
구 변경해도 API 호출이 안 되는 문제
🔴 문제

구 단위 조회로 바꾼 후, 지도를 아무리 움직여도 API 호출 로그가 하나도 안 찍혔다. 콘솔을 열어봐도 네트워크 요청 자체가 발생하지 않았다.

🟡 원인 (3가지 겹침)
  • 원인 1 — stale 클로저: 맵 초기화와 이벤트 리스너 등록을 하나의 useEffect에 넣었더니, onBoundsChanged가 의존성 배열에 없어서 리스너가 최초 1회만 등록되고 갱신되지 않았다.
  • 원인 2 — 네이버 맵 객체 키 오류: b.ne, b.sw로 접근했는데 실제 네이버 맵 객체는 b._ne, b._sw (언더스코어 필요)였다.
  • 원인 3 — API 방식 자체가 잘못됨: bounds 좌표로 매번 호출하는 게 아니라 구 이름으로 호출하는 구조였는데, 원인 1 수정하면서 X/Y 좌표 방식으로 잘못 바꿔버렸다.
🟢 해결

useEffect를 두 개로 분리(맵 초기화 / 리스너 등록), 네이버 맵 키 수정, API 방식을 구 이름 기반으로 확정했다.

구 선택 지도 이동 fetchStationsByDistrict("강동구") GET /api/wattup/map/강동구 마커 렌더링
💡 깨달은 것 React useEffect 의존성 배열은 꼼꼼히 챙겨야 한다. 하나 빠지면 stale 클로저 문제가 생긴다. 외부 라이브러리 객체 구조는 공식 문서를 직접 확인해야 한다.
Docker & Deploy
🐳
Troubleshooting 04
Docker 이미지 태그 관리 — latest의 함정
🔴 문제

이미지를 latest 태그 하나로만 관리했더니, 팀원에게 공유할 때마다 어떤 버전인지 헷갈렸다. 문제가 생겼을 때 이전 상태로 돌아갈 수도 없었다.

🟡 원인

latest는 "가장 최신"을 보장하는 게 아니라 그냥 기본 이름일 뿐이다. 같은 태그로 덮어쓰면 이전 이미지는 이름 없는 Dangling Image가 되어 복구가 불가능해진다.

🟢 해결 — 이중 태그 전략

버전 태그(v1.1)와 latest를 동시에 관리하는 방식으로 변경했다.

# 버전 태그로 빌드
docker build -t soojungchoi98/wattup-fe:v1.1 .

# 같은 이미지에 latest 별칭 추가
docker tag soojungchoi98/wattup-fe:v1.1 soojungchoi98/wattup-fe:latest

# 둘 다 푸시 (동일한 데이터, 용량 중복 없음)
docker push soojungchoi98/wattup-fe:v1.1
docker push soojungchoi98/wattup-fe:latest
💡 깨달은 것 도커 태그는 포인터다. 한 번 푸시한 버전 태그는 절대 수정하지 않고, 변경사항은 v1.2처럼 새 버전으로 만드는 것이 원칙이다.
OCI Cloud Deploy
☁️
경험 공유
Vercel vs Docker — OCI로 배포해보기
🤔 고민

프론트엔드 배포 방법으로 Vercel과 Docker 이미지 두 가지를 고민했다. Vercel은 간편하지만, 이번 프로젝트는 백엔드/DB 전체가 Docker로 구성되어 있었다.

🟢 결과

팀원의 아이디어로 OCI(Oracle Cloud Infrastructure) 무료 티어를 활용하는 방안이 나왔다. 직접 계정을 만들고 Ingress 포트를 열어서 Docker 이미지를 클라우드에 배포하는 경험을 해볼 수 있었다.

💡 깨달은 것 Vercel 클릭 한 번과는 다르게, 포트 개방부터 컨테이너 실행까지 직접 하면서 배포의 흐름을 제대로 이해할 수 있었다.
🔥
Troubleshooting 06
OCI 배포 — Docker인 줄 알았더니 Podman이었다
🔴 문제

OCI 인스턴스에 docker run으로 프론트엔드 컨테이너를 올렸는데, 외부 IP로 접근이 아예 안 됐다. localhost에서는 HTML이 정상 반환됐고, 오라클 콘솔 Security List에서 80/443 포트도 열어뒀는데 계속 No route to host 오류가 났다.

🟡 원인 (3가지 겹침)
  • 원인 1 — Docker가 아닌 Podman: OCI는 Red Hat 계열 리눅스라 docker 명령어를 치면 Podman이 에뮬레이션 모드로 실행된다. CLI는 같지만 내부 네트워크 처리 방식이 달라 방화벽 충돌이 생겼다.
  • 원인 2 — Security List Source Port 설정 오류: Source Port를 80, 443으로 지정했는데, 클라이언트는 임의의 Ephemeral Port로 요청을 보내기 때문에 대부분의 요청이 차단됐다.
  • 원인 3 — nftables FORWARD 체인 REJECT 규칙: OCI 리눅스는 iptables와 nftables가 동시에 동작한다. iptables에서 REJECT 규칙을 지워도 nftables가 별도로 포워딩을 막고 있었다.
🟢 해결 과정
# Step 1. localhost 정상 확인
curl http://localhost:80

# Step 2. iptables REJECT 규칙 제거
sudo iptables -D INPUT -j REJECT --reject-with icmp-host-prohibited
sudo netfilter-persistent save

# Step 3. Security List → Source Port Range를 All로 수정

# Step 4. nftables FORWARD 체인 확인 후 flush
sudo nft list ruleset
sudo nft flush ruleset
sudo netfilter-persistent save

# Step 5. 컨테이너 재시작
sudo podman stop wattup-fe && sudo podman rm wattup-fe
sudo podman run -d --name wattup-fe -p 80:80 docker.io/soojungchoi/wattup-fe:v1.4
✅ 결과

외부 IP로 HTML 정상 반환 확인. 총 3가지 원인이 겹쳐있어서 하나씩 제거하는 과정이 필요했다.

항목 Docker (Ubuntu) Podman (Oracle Linux)
데몬dockerd 상시 실행데몬 없음 (daemonless)
방화벽iptables 단일iptables + nftables 동시
CLIdocker 명령어docker 에뮬레이션
네트워크docker0 브리지CNI 기반
💡 깨달은 것 배포 전 docker info로 런타임 확인 필수. OCI는 iptables와 nftables가 동시에 동작하니 두 곳 모두 확인해야 한다. nft flush ruleset은 모든 규칙을 날리기 때문에 SSH 세션을 미리 하나 더 열어두고 실행할 것.

📎 GitHub Repository

🔗 wattup-fe 보러가기
728x90
반응형