ADR‐012: 앱 화면은 하나의 웹뷰 셸이 그린다 - Moadong/moadong GitHub Wiki

작성일: 2026-09-27 | 상태: Accepted
결정일자: 2026-06-07

관련: ADR‐009: 웹뷰 이미지 업로드는 브리지를 경유하지 않는다

요약

  • 문제: 앱의 바텀 탭, 헤더, 구독·메뉴 화면이 네이티브로 남아 있어서, 이 화면을 바꾸는 일은 앱 릴리즈(간격 1~9주)를 기다려야 했습니다. 홍보·알림 위치를 바꿔 보는 실험도 여기에 막혀 있었습니다.
  • 결정: 네이티브 탭을 없애고 앱을 웹뷰 하나로 된 셸로 만듭니다. 바텀 내비게이션까지 웹이 그리고, 앱은 푸시·구독 API·공유 같은 네이티브 기능만 브리지로 제공합니다. 구버전 앱은 호환 분기 대신 강제 업데이트로 막습니다.
  • 결과: 전환(1.6.0, 2026-06-13) 뒤 3개월 동안 웹은 프로덕션에 29번 배포됐고, 화면 변경 때문에 앱을 릴리즈한 적은 없습니다. 그 사이 앱 릴리즈 2번(1.7.0, 1.7.1)은 둘 다 웹뷰에 토큰을 넣는 브리지 변경이었습니다.

왜 이 결정이 필요했나요?

모아동 앱은 화면을 조금씩 웹으로 옮겨 왔습니다. 동아리 상세는 2026-01에(RN #5), 홈은 2026-05에(RN #15) 웹뷰가 됐습니다. 그래도 바텀 탭, 화면 헤더, 구독 목록, 메뉴(더보기)는 네이티브로 남아 있었습니다.

네이티브로 남은 화면은 바꿀 때마다 앱을 새로 내야 했습니다. 셸 전환 전 앱 릴리즈 간격은 짧게는 1주(1.1.0 → 1.2.0, 1/20 → 1/27), 길게는 9주(1.4.0 → 1.5.0, 3/1 → 5/3)였고, 스토어 심사와 사용자 업데이트 대기는 그 뒤에 따로 붙습니다.

직접적인 계기는 홍보·알림 위치를 바꿔 보는 실험이었습니다. 실험 대상인 바텀 탭이 네이티브라서, 변형을 하나 바꿀 때마다 앱을 릴리즈해야 했습니다(RN #21).


어떤 결정을 내렸나요?

네이티브 바텀 탭을 없애고, 앱은 홈 웹뷰 하나를 전체 화면으로 띄우는 셸이 됩니다.

  • 바텀 내비게이션(홈·구독·메뉴)과 구독·메뉴 페이지는 웹이 그립니다(웹 #1643). 웹과 앱 웹뷰는 같은 라우트를 쓰고, 앱 안인지는 UA의 MoadongApp 하나로만 가립니다.
  • RN은 app/(tabs)와 네이티브 구독·메뉴 화면을 지우고 app/index.tsx의 홈 웹뷰 하나만 남깁니다(RN #21).
  • 앱이 계속 맡는 건 웹이 할 수 없는 일뿐입니다: 푸시 알림과 FCM 토큰, 구독 API 호출(SUBSCRIBE_TOGGLE → SUBSCRIBE_RESULT), 공유, 외부 링크, 앱 버전 조회.
  • 화면 이동은 앱 브리지(NAVIGATE_WEBVIEW) 대신 웹의 React Router가 합니다(웹 #1662). 뒤로가기는 웹뷰 히스토리를 따릅니다. 상세의 < 버튼은 NAVIGATE_BACK, Android 하드웨어 백은 goBack, iOS는 엣지 스와이프를 켰습니다.

구버전 앱은 강제 업데이트로 막습니다. 구버전 앱이 새 웹을 띄우면 네이티브 탭과 웹 바텀 내비가 겹쳐 두 번 보이고, 웹뷰 안 상세에서 뒤로가기가 먹지 않습니다. 호환 분기로 피하려면 웹이 구버전 앱용 화면을 계속 그려야 해서, 지울 수 없는 부채가 됩니다.

앱에는 이미 강제 업데이트 장치(Firebase Remote Config min_supported_version + ForceUpdateDialog)가 있어 새 코드가 필요 없었습니다.

  • 앱 버전을 1.6.0으로 올려 스토어에 냅니다.
  • 그다음 min_supported_version을 1.6.0으로 올립니다(2026-06-14). 순서가 바뀌면 업데이트를 받을 수 없는 사용자까지 잠깁니다.
  • 웹의 /webview/* 경로는 구버전 앱이 처음 여는 주소라 새 경로로 리다이렉트만 남겨 둡니다.

다른 대안들:

  • ❌ 네이티브 탭 유지, 탭 안 화면만 웹뷰: 이미 하던 방식입니다. 탭과 헤더를 바꾸는 일은 계속 앱 릴리즈에 묶이고, 실험 대상이 바로 그 탭이었습니다.
  • ❌ 구버전 호환 분기: UA에 셸 여부 토큰을 넣고, 웹이 구버전용 화면을 따로 그리고, RN의 네이티브 상세 화면을 계속 유지하는 안입니다. 구버전 사용자가 사라질 때까지 두 벌을 관리해야 하고, 사라졌는지 확인할 방법도 마땅치 않습니다.

어떤 효과가 있나요?

전환(RN 1.6.0, 2026-06-13) 뒤 3개월 동안 웹은 develop-fe → main으로 29번 배포됐고, 화면 변경 때문에 앱을 릴리즈한 적은 없습니다. 이 기간 앱 릴리즈는 1.7.0(08-15, 우체통 답장 알림 대상을 가리려고 웹뷰에 학생 토큰 주입)과 1.7.1(08-18, 토큰 처리 수정과 빌드 설정)뿐입니다. 홈을 통째로 바꾼 메인 개편 실험(2026-09, PR #2034로 종료)도 웹 배포만으로 앱 안의 홈까지 바뀌었습니다.

장점:

  • 앱 안의 모든 화면이 웹 배포 주기를 따릅니다.
  • 웹과 앱 웹뷰가 같은 라우트, 같은 컴포넌트를 씁니다. 화면마다 웹 버전과 웹뷰 버전을 따로 두지 않습니다.
  • 탭과 헤더를 바꾸는 실험도 웹 배포만으로 돌릴 수 있습니다.

단점/트레이드오프:

  • 앱이 화면 사이에서 해 주던 일을 웹이 떠안았습니다. 예를 들어 상세 화면의 구독 초기 상태는 원래 앱이 URL(?is_subscribed)에 실어 넘겼는데, 셸 안에서는 웹이 직접 넘겨야 합니다. 전환 중 이 틈에서 구독 표시가 어긋나는 버그가 나와 같은 릴리스에서 고쳤습니다(웹 #1665). 지금도 초기값은 호출하는 쪽이 URL로 넘기는 구조라, 새 진입 경로가 이 값을 빠뜨리면 같은 문제가 다시 생깁니다.
  • 네이티브 스택이 주던 뒤로가기와 제스처를 웹뷰 히스토리로 다시 맞춰야 했습니다.
  • 브리지 메시지는 여전히 앱 릴리즈가 필요합니다. 새 네이티브 기능이 필요하면 앱과 웹을 함께 바꿔야 하고, 순서가 틀리면 구버전 앱이 모르는 메시지를 버립니다. 1.7.0·1.7.1이 이 경우였습니다.
  • 첫 화면이 웹 로딩에 달려 있습니다. 홈 웹뷰를 미리 띄워 두는 것(RN #24)으로 대응하고 있습니다.

다시 볼 조건: 웹으로는 만들 수 없는 화면(오프라인 동작, 카메라·지도 같은 네이티브 UI)이 핵심 기능이 되면, 그 화면만 네이티브로 두고 셸에서 띄우는 방식을 검토합니다. 셸 전체를 되돌리지는 않습니다.


참고 자료

  • 웹: #1643 웹/웹뷰 통합과 웹 바텀 내비게이션, #1662 웹뷰 내부 라우팅 통일, #1665 구독 상태 동기화
  • 앱: RN #21 네이티브 탭 제거와 단일 웹뷰 셸, RN #26 브리지 보완, RN #27 1.6.0 릴리스
⚠️ **GitHub.com Fallback** ⚠️