Capacitor로 PWA를 iOS 앱으로 출시하기
Plan Do!는 React로 만든 PWA였는데, 사용자들이 iOS 앱을 찾기 시작하면서 네이티브 출시를 고민하게 됐습니다.
선택지는 둘이었어요.
- React Native로 다시 짜기 — 네이티브에 가까운 성능, 다만 코드베이스가 둘로 갈라짐
- Capacitor로 기존 웹을 감싸기 — 한 코드베이스 유지, 네이티브 기능은 플러그인으로
1인 개발 환경에서 두 개의 코드베이스를 유지할 자신이 없어서 Capacitor로 갔습니다. 지금 시점에서 보면 잘한 결정이었어요.
첫 셋업
npm install @capacitor/core @capacitor/ios
npx cap init
npx cap add ios
명령어 자체는 단순한데, 실제로 App Store에 올라가는 빌드가 나오기까지 며칠 걸렸습니다. Capacitor 8.3 기준으로 진행했고, 플러그인은 필요한 것만 골라서 넣었어요.
@capacitor/push-notifications— APNs 토큰 발급@capacitor/geolocation— 위치 기반 알림용@capacitor/keyboard— iOS 키보드 높이 추적@capacitor/app— 앱 백그라운드/포그라운드 전환 감지@capacitor/browser— 외부 링크@capacitor/haptics— 터치 피드백@capacitor-community/apple-sign-in— Apple 로그인
플러그인을 늘릴수록 빌드 시간과 리뷰 리스크가 같이 올라가서, 안 쓰는 건 깔지 않으려고 했어요.
Safe-area와 키보드 — 가장 시간 많이 잡아먹은 부분
데스크탑 Safari에서 잘 보이던 화면이 iOS에서는 미묘하게 어긋났습니다. 노치 아래로 콘텐츠가 잘리거나, 키보드가 올라올 때 입력창이 가려지는 식이었어요.
해결은 두 군데에서 했습니다.
capacitor.config.json에서contentInset: "never"+resize: "native"- CSS에서
env(safe-area-inset-*)를 일관되게 적용
특히 키보드 처리는 resize 옵션을 잘못 잡으면 viewport가 두 번 줄어드는 현상이 있어서
useKeyboardOffset 같은 훅으로 한 번에 컨트롤하게 했습니다.
Universal Links / Deep Links
푸시 알림에서 특정 화면으로 이동시키려면 Deep Link가 필요했어요.
plando:// 스킴을 Info.plist의 CFBundleURLTypes에 잡아두고,
React 쪽에서는 DeepLinkHandler 컴포넌트 하나에서 모든 진입을 처리합니다.
- 워크스페이스 초대(
/join) - 특정 todo로 바로 이동
- 위젯에서 들어오는 진입
여기서 한 번 잘 깔아두니까 위젯에서 들어오든 푸시에서 들어오든 같은 코드가 처리하게 돼서 한참 후에 위젯 기능을 붙일 때 추가 작업이 거의 없었어요.
백그라운드 복귀 — 의외로 작은 디테일이 큰 차이
iOS는 앱이 백그라운드에서 한참 있다가 돌아오면, 화면은 그대로인데 서버 상태와 어긋난 상황이 흔히 생깁니다. todo가 다른 기기에서 바뀌었거나, 새 DM이 도착했거나.
Capacitor.App.addListener('appStateChange')에 훅을 걸어서
포그라운드 복귀 시점에 가벼운 재동기화를 트리거하게 했습니다.
사용자가 돌아왔을 때 자연스럽게 최신 상태를 보는 인상이 이게 결정합니다.
결정 — 단순함을 더 우선
빌드 워크플로우와 코드 구조에서 일관되게 적용한 원칙들:
- WebView 전용 화면을 따로 두지 않기 — 같은 라우트를 그대로, Capacitor 환경만 분기 처리
- 네이티브 플러그인은 최소화 — 카메라·푸시·위치·키보드 정도. 나머진 웹 표준
platformUtils.js한 곳에 분기 모으기 —Capacitor.getPlatform()호출이 코드에 흩어지지 않게
정리
같은 코드 한 줄이 웹과 iOS 양쪽에 영향을 주는 구조라 변경의 무게는 커졌습니다. 하지만 그만큼 한 사람이 양쪽을 동시에 끌고 갈 수 있게 되었어요.
App Store 심사는 한 번에 통과하진 않았고, 권한 설명 문구를 다듬는 과정에서 몇 번의 왕복이 있었습니다. 필요한 권한만, 왜 필요한지 명확히 적는 게 가장 효과적이었던 것 같아요.
Capacitor가 React Native만큼 빠르지는 않지만, 혼자 만드는 사람에게는 충분히 합리적인 선택이라는 게 지금까지의 결론입니다.