카공지도(카페 공부 지도)를 About에서 떼어내 별도 앱으로 출시하려고 두 저장소, app과 cafe-map-app을 열었다. 둘 다 이미 분리되어 있었고 빌드도 각자 돌아갔다. 그런데 각 저장소가 실제로 무엇을 About으로, 무엇을 카공지도로 알고 있는지 레이어를 하나씩 열어보니, 앱을 구별하는 값 다섯 개 — Android applicationId, Android 네이티브 패키지, iOS Firebase Bundle ID, React Native 컴포넌트 등록명, 딥링크 URL 스킴 — 중 네 개가 두 앱에서 같은 값을 쓰고 있었다.
처음 가정한 것
이름 하나만 다르게 바꾸면 두 앱이 나뉜다고 가정했다. app.json의 name 필드만 보고 시작했다.
그런데 당시 About이 되어야 할 저장소의 app.json은 name: "카공지도"였고, 같은 저장소의 Android MainActivity.getMainComponentName()은 "어바웃"을 요청하고 있었다.
// android/app/src/main/java/.../MainActivity.java
protected String getMainComponentName() {
return "어바웃";
}React Native는 JS 쪽에서 AppRegistry.registerComponent로 등록한 이름과 네이티브가 요청하는 모듈명이 같아야 부팅한다. 두 이름이 다르면 화면이 뜨기 전에 번들 로드부터 실패한다. 이름 하나 바꾸는 걸로 끝날 문제가 아니었다.
실제로 측정한 것
두 저장소의 다섯 레이어를 전부 읽었다.
| 레이어 | app (→ About) | cafe-map-app (→ 카공지도) |
|---|---|---|
app.json name | 카공지도 | 어바웃 |
Android strings.xml app_name | 어바웃 | 카공지도 |
Android applicationId | com.about.studyaboutclubapp | club.about20s.cafemap |
| Android 네임스페이스(패키지 경로) | com.about.studyaboutclubapp | com.about.studyaboutclubapp |
| iOS Firebase Bundle ID | club.about20s.cafemap | club.about20s.cafemap |
| iOS URL 스킴 | kagongmap | kagongmap |
| WebView 진입 URI | study-about.club/ | study-about.club/cafe-map |
일곱 개 값 중 마지막 WebView URI 하나만 제대로 갈라져 있었다. 나머지는 두 앱이 서로 반대 값을 들고 있거나(첫 두 줄), 아예 같은 값을 공유하고 있었다(뒤 네 줄).
git으로 각 레이어에 언제 café-map 값이 들어왔는지 추적했다. build.gradle, app.json, App.tsx 세 파일 모두 같은 커밋(6월 6일, 메시지는 .) 하나에서 들어와 있었다. 카공지도를 급하게 만들려고 About 저장소를 복제하면서, 세 레이어를 한 번에 café-map 값으로 바꿔 넣은 흔적이었다.
그 시점 카공지도는 인스타그램에서 콘텐츠가 유행하며 누적 조회수 1,000만 회를 넘기고 있었다. 하루 접속자가 수만 명 단위로 뛰었고, 카페 프랜차이즈 ‘카공족’과의 제휴도 준비하던 때였다. 커밋 메시지를 .으로 남기고 세 레이어를 한 번에 바꿔 넣은 건, 트래픽이 이미 몰리는 상황에서 정식 앱을 최대한 빨리 스토어에 올려야 했기 때문이다.
판단과 근거
폴더 이름은 믿지 않기로 했다. 대신 네이티브 레이어의 모듈명을 기준으로 삼았다. app.json은 café-map을 만들 때 복사되며 자주 바뀌지만, MainActivity.java나 AppDelegate.mm은 초기 세팅 이후 손대는 일이 드물어서 신뢰도가 더 높다고 판단했다.
app의 네이티브 모듈명은"어바웃"→ 이 저장소가 About이어야 한다.cafe-map-app의 네이티브 모듈명도"어바웃"이었다 — 즉 이 저장소는 아직 café-map으로 전환된 적이 없고, About을 그대로 복제한 상태였다.
두 저장소를 각자 다른 방향으로 고쳤다. app은 JS 레이어(app.json, App.tsx)를 네이티브가 요청하는 About 값에 맞춰 되돌렸다. App.tsx에 하드코딩돼 있던 kagongmap 열두 곳을 about20s로 바꾸고, AndroidManifest.xml에 위치 권한(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION)을 추가했다. 지도 기능이 About 쪽에도 일부 남아 있었기 때문이다.
cafe-map-app은 반대 방향이다. applicationId를 club.about20s.cafemap으로, 딥링크 스킴을 kagongmap으로 바꿨다. 다만 여기서는 네이티브 모듈명("어바웃")까지는 건드리지 않았다 — app.json의 name과 네이티브 모듈명 두 값이 서로 일치하기만 하면 앱은 정상 부팅하기 때문에, 브랜드명이 틀려도 빌드와 배포는 막지 않는다.
우선순위 밖에 둔 것
두 가지는 그 자리에서 고치지 않기로 했다.
cafe-map-app의 RN 컴포넌트 등록명은 "어바웃"으로 남겨뒀다. 딥링크 스킴은 kagongmap으로 바뀌었지만 앱 내부 컴포넌트 이름표는 그대로 둔 것이다. 두 저장소의 Android 네임스페이스(Java 패키지 경로)도 com.about.studyaboutclubapp으로 같게 뒀다 — applicationId가 다르므로 플레이스토어에는 별개 앱으로 올라가지만, 소스 트리 안에서는 같은 패키지 아래 있다.
둘 다 부팅 크래시로 이어지는 값이 아니었다. applicationId, 딥링크 스킴, Firebase 등록처럼 사용자가 체감하는 값은 이미 갈라진 상태였고, 남은 두 개는 코드를 여는 개발자만 보는 값이었다. 트래픽이 몰리는 상황에서 순서를 정해야 했고, 부팅을 막는 값부터 고쳤다.
결과
같은 날, 두 앱 모두 AAB 빌드를 올렸다. 버전 코드 충돌(169, 172, 175)을 세 번 겪으며 번호를 올렸다. 카공지도 쪽에서 타겟 API 35 관련 오류가 떴고, About 쪽은 오류 없이 통과했다. 카공지도 오류를 마저 잡은 뒤 두 앱 다 커밋·푸시로 마무리했다.
| 이전 | 이후 | |
|---|---|---|
| applicationId | 두 앱이 공유 | 앱마다 별도 |
네이티브 모듈명 불일치로 인한 부팅 실패 (app) | 있음 | 해소 |
App.tsx에 하드코딩된 kagongmap (About 쪽) | 12곳 | 0곳 |
다섯 레이어 중 부팅과 직결된 셋을 그 자리에서 바로잡았다. 나머지 둘은 사용자가 체감하지 않는 값이라 다음 순서로 넘겼다.