출시 준비를 "스토어 심사를 통과하는가"로만 보면 절반만 보는 것이다. 실제로 문제가 되는 건 올린 뒤다. 디버그에서 멀쩡하던 화면이 릴리스 빌드에서 흰 화면이 되고, 심사에서 막히는 지점은 대부분 코드가 아니라 정책 신고 항목이며, 출시 후에 가장 아쉬운 건 크래시가 났는데 그 사실조차 몰랐다는 상황이다.
이 글은 그 세 가지를 단계별로 정리한 체크 리스트다. 릴리스 빌드에서만 드러나는 문제, 스토어 정책 때문에 리젝당하는 지점, 그리고 운영을 시작하고 나서 후회하는 부분을 순서대로 다룬다. 날짜가 걸린 정책 항목은 2026년 9월 기준이다.
체크 리스트를 돌리기 전에 검증 환경부터 맞춰야 한다. 아래 세 가지가 어긋나면 그 뒤의 모든 확인이 의미가 없다.
릴리스 빌드로만 검증한다. 디버그 빌드는 Metro 서버에 의존하고 JS를 런타임에 평가한다. 프로덕션은 번들이 앱 안에 포함되고 최적화와 난독화가 켜진 상태다. 실행 조건 자체가 다르기 때문에 "디버그에서는 됐다"는 아무 근거가 되지 못한다. iOS는 --mode Release, Android는 assembleRelease 또는 bundleRelease 산출물로 테스트한다.
진짜 콜드 스타트로 검증한다. 개발 중에 쓰는 reload는 네이티브 프로세스를 살려둔 채 JS만 다시 초기화한다. 영속 저장 누락이나 초기화 순서 버그는 이 방식으로는 절대 재현되지 않는다. 앱을 스와이프로 완전히 종료한 뒤 다시 실행해서 확인한다.
실기기에서 검증한다. 시뮬레이터와 에뮬레이터는 메모리, CPU, 네트워크 조건이 실기기와 다르다. 특히 저사양 안드로이드 기기는 반드시 한 대 포함한다. 리스트 스크롤이 버벅이는지, 콜드 스타트가 몇 초 걸리는지는 그 기기에서만 드러난다.
__DEV__ 분기 코드가 프로덕션으로 새어 들어가지 않는지 확인한다. 디버그 전용 로깅, 목업 데이터, 테스트 플래그가 대표적인 후보다.console.log는 릴리스 빌드에서 제거한다. Hermes에서도 과도한 로깅은 비용이고, 민감 정보가 그대로 찍히는 사고도 흔하다. babel-plugin-transform-remove-console을 릴리스 환경에만 적용한다.versionName / CFBundleShortVersionString과 빌드 식별자인 versionCode / CFBundleVersion을 분리하고, 빌드 넘버 자동 증가는 CI에 넣는다../gradlew bundleRelease로 만든다.minifyEnabled true, shrinkResources true. 다만 켜는 순간 리플렉션에 의존하는 라이브러리가 깨질 수 있다. proguard-rules.pro에 keep 규칙을 추가하고, R8을 켠 릴리스 빌드로 전 기능 회귀 테스트를 한 번 돌린다. 난독화 관련 버그는 거의 예외 없이 릴리스에서만 나타나기 때문에, 이 테스트를 건너뛰면 사용자가 먼저 발견한다.android:debuggable이 릴리스에서 false인지, android:usesCleartextTraffic이 의도치 않게 열려 있지 않은지 확인한다.Android는 업로드 키와 앱 서명 키를 구분해야 한다. Play App Signing을 쓰면 구글이 앱 서명 키를 관리하고 개발자는 업로드 키만 보관한다. 키스토어와 비밀번호를 잃어버리면 같은 앱으로 업데이트하는 경로가 영구적으로 막힌다. 새 패키지명으로 다시 올리는 것 말고는 방법이 없고, 그동안 쌓은 설치 수와 리뷰는 전부 사라진다. 키스토어와 비밀번호는 비밀 관리 시스템에 백업하고 저장소에는 절대 커밋하지 않는다.
iOS는 배포 인증서와 프로비저닝 프로파일을 관리한다. fastlane match로 팀의 서명 자산을 한곳에 모아두면 만료나 재발급 때문에 릴리스가 멈추는 일을 줄일 수 있다. 인증서 만료일은 캘린더에 등록해 둔다.
두 플랫폼 모두 서명에 쓰는 비밀값은 CI 시크릿으로만 주입한다. 키스토어 비밀번호, App Store Connect API 키가 코드나 저장소에 들어가면 그 순간 전부 폐기하고 재발급해야 한다.
심사에서 가장 자주 막히는 지점들이다. 코드 문제가 아니라 신고 항목과 빌드 설정 문제라서, 출시 직전에 발견하면 일정이 통째로 밀린다.
타깃 API 레벨. 2026년 8월 31일부터 신규 앱과 업데이트 모두 Android 16, 즉 API 36 이상을 타깃해야 제출할 수 있다. 아직 준비가 안 됐다면 2026년 11월 1일까지 연장을 신청할 수 있지만, 연장은 유예일 뿐 면제가 아니다. 구글은 "최신 메이저 릴리스로부터 1년 이내"라는 규칙을 매년 적용하므로, targetSdkVersion 상향은 연례 작업으로 잡아두는 편이 낫다.
16KB 페이지 크기. 2025년 11월 1일부터 Android 15 이상을 타깃하는 앱은 네이티브 라이브러리가 16KB 페이지 정렬을 지원해야 한다. 일회성 연장 마감도 이미 지났다. React Native 코어는 진작에 대응됐지만 문제는 서드파티다. 카메라, 결제, 영상, 암호화, AR 계열 네이티브 모듈 중 하나라도 비호환이면 업로드 자체가 막힌다. AAB를 Play Console에 올려 메모리 페이지 크기 항목을 확인하거나, 아래처럼 직접 검사한다.
readelf -h lib*.so | grep 'Page size'
값이 16384가 아니면 비호환이다. NDK 28 이상으로 재빌드된 최신 버전으로 올리고, 업데이트가 끊긴 라이브러리라면 대체재를 찾아야 한다. 이 작업은 라이브러리 메이저 업그레이드로 이어지는 경우가 많아서 출시 2주 전에 시작하면 늦다.
데이터 안전 폼. 수집하고 공유하는 데이터 유형을 정확히 신고한다. 실제 동작과 다르면 리젝은 물론 앱 정지 사유가 된다. 직접 수집하지 않더라도 광고나 분석 SDK가 수집하는 항목까지 포함해야 한다.
권한. 선언한 권한이 실제 기능과 맞는지 확인한다. 백그라운드 위치나 접근성 같은 민감 권한은 정당한 사유를 별도로 소명해야 한다.
프라이버시 매니페스트. 2024년 5월 1일부터 PrivacyInfo.xcprivacy가 필수다. 파일 타임스탬프, 디스크 여유공간, 시스템 부팅 시간, UserDefaults처럼 사유 선언이 필요한 API를 쓰면 매니페스트에 사유를 적어야 하고, 없으면 App Store Connect가 업로드 단계에서 ITMS-91061 같은 오류로 거부한다. React Native는 코어와 상당수 서드파티 SDK가 이 API를 건드리므로, 앱 매니페스트뿐 아니라 사용하는 SDK들의 매니페스트까지 갖춰져 있어야 한다.
App Tracking Transparency. IDFA 등으로 추적을 한다면 ATT 권한 요청과 사유 문구가 반드시 있어야 한다.
프라이버시 영양성분 표시. 실제 수집 항목과 일치하게 작성한다. Play의 데이터 안전 폼과 내용이 어긋나면 그것도 문제가 된다.
권한 사용 설명 문자열. NSCameraUsageDescription처럼 사용하는 모든 권한에 대한 설명이 채워져 있어야 한다. 비어 있으면 리젝된다. 라이브러리가 요구하는데 정작 앱에서는 안 쓰는 권한이 남아 있는 경우도 흔하니, 최종 빌드의 Info.plist를 직접 열어 확인한다.
API 키와 시크릿을 JS 번들에 넣지 않는다. RN 번들은 추출하기 쉽다. react-native-config에 넣은 값도 빌드 산출물에서 꺼낼 수 있다고 전제해야 한다. 진짜 비밀은 서버에 두고 클라이언트는 토큰 교환만 담당하게 만든다.
토큰과 자격증명은 iOS Keychain, Android Keystore를 사용하는 보안 저장소에 저장한다. react-native-keychain이 대표적이다. AsyncStorage와 MMKV는 암호화되지 않은 일반 저장소이므로 민감 정보를 평문으로 두면 안 된다. MMKV를 쓴다면 암호화 옵션을 켠다.
통신은 HTTPS만 허용한다. 금융이나 의료처럼 보안 요구 수준이 높은 앱이라면 SSL 피닝을 검토한다. 루팅과 탈옥 탐지, 디버거 탐지, 난독화 강도는 앱의 위협 모델에 맞춰 결정할 문제다. 모든 앱이 여기까지 갈 필요는 없다.
New Architecture와 Hermes V1이 켜져 있는지 확인한다. React Native 0.84부터 Hermes V1이 기본값이 되었고 레거시 아키텍처는 제거됐다. 최신 버전에서 새로 만든 프로젝트라면 신경 쓸 일이 없지만, 오래된 템플릿에서 마이그레이션한 프로젝트는 설정이 남아 있는 경우가 있으니 한 번 확인한다.
화면을 넘어가는 길이의 리스트는 FlashList를 쓴다. FlatList도 동작은 하지만, 항목이 많고 높이가 제각각인 리스트에서는 FlashList 쪽이 스크롤 성능 차이가 확실하다.
애니메이션은 Reanimated worklet으로 UI 스레드에서 돌린다. JS 스레드에서 프레임을 계산하면 다른 작업 하나에 애니메이션이 통째로 끊긴다.
출시 전에 프로파일링을 한 번은 한다. Hermes Sampling Profiler나 React DevTools Profiler를 쓴다. "어딘가 느리다"의 원인은 대개 특정 컴포넌트 하나의 불필요한 리렌더인데, 이건 코드를 아무리 읽어도 잘 안 보인다. 측정하면 5분이면 찾는다.
그 외에 이미지 캐싱과 리사이즈가 적절한지, 큰 이미지의 네이티브 디코딩 부담이 없는지 점검한다. 콜드 스타트 지표는 P75 화면 로드 시간처럼 기준선을 정해 측정해 둬야 다음 릴리스에서 나빠졌는지 알 수 있다. 메모리 누수는 useEffect cleanup에서 리스너, 타이머, 구독을 해제했는지 확인하는 것이 기본이다.
크래시 리포팅은 출시 전에 붙인다. Sentry나 Firebase Crashlytics 중 하나면 된다. 소스맵과 dSYM 업로드는 CI에 자동화한다. 이걸 안 붙이고 출시하면 사용자 크래시를 눈감고 맞이하는 셈이고, 리뷰에 "자꾸 꺼져요"만 남는다.
처리되지 않은 Promise rejection과 전역 에러를 잡아 리포팅하는 핸들러도 함께 붙인다. New Architecture에서도 처리되지 않은 JS 예외는 앱 종료로 이어질 수 있다.
에러 바운더리로 화면 단위 크래시를 격리한다. 한 화면의 렌더 오류가 앱 전체를 흰 화면으로 만들지 않게 하는 최소한의 방어선이다.
가입이나 결제 같은 핵심 퍼널에는 이벤트 로깅을 심어둔다. 출시 후 어디서 이탈하는지 보려면 데이터가 처음부터 쌓여 있어야 한다.
환경 분리. dev, staging, prod API 엔드포인트가 빌드 타입별로 정확히 갈리는지 확인한다. 프로덕션 빌드가 staging 서버를 바라보는 사고는 생각보다 자주 일어난다.
버전 호환성. 앱은 서버보다 오래 살아남는다. 사용자가 업데이트를 안 하면 1년 전 버전이 그대로 돌아간다. 구버전 앱이 신버전 서버와 통신해도 깨지지 않도록 후방 호환을 설계하고, 그럼에도 막아야 할 상황을 위해 강제 업데이트 게이트를 미리 넣어둔다. 이건 나중에 추가할 수 없다. 게이트가 없는 버전은 영원히 게이트가 없다.
실패 처리. 네트워크 없음, 타임아웃, 5xx 각각에 대한 화면을 정의한다. 재시도는 멱등성 키로 중복 요청을 막는다. 결제 API를 재시도하다 이중 결제가 되는 사고가 여기서 나온다.
타임존과 로케일. 서버 시간 기준으로 처리하는지, 다국어와 RTL 대응이 필요한지 점검한다.
런타임 권한은 거부 시나리오를 반드시 처리한다. 사용자가 카메라나 위치를 거부해도 앱이 진행 가능한 대체 경로가 있어야 한다. 거부하면 아무것도 못 하고 멈추는 화면은 리젝 사유가 되기도 한다.
권한 요청은 맥락이 생긴 시점에 한다. 앱 첫 실행에 필요한 권한을 전부 몰아서 요청하면 승인율이 눈에 띄게 떨어진다.
개인정보처리방침 URL은 스토어 등록 정보와 앱 내부 양쪽에 둔다. 스토어 필수 항목이다. 계정 생성이 가능한 앱이라면 앱 안에서 계정과 데이터를 삭제할 수 있는 경로를 제공해야 한다. 양쪽 스토어 모두 정책으로 요구한다.
단계적 출시. 한 번에 100%로 풀지 않는다. Play와 App Store 모두 단계적 출시를 지원하니 1~10%부터 시작해 크래시율을 보면서 확대한다. 문제가 생겼을 때 영향받는 사용자 수가 달라진다.
OTA 업데이트 경로. JS 핫픽스 경로를 미리 마련해 두면 급한 버그를 스토어 심사 없이 고칠 수 있다. Microsoft App Center의 CodePush는 2025년에 서비스가 종료됐으므로, 지금은 Expo Updates나 자체 호스팅 서버, 서드파티 서비스 중에서 고르게 된다. 어느 쪽이든 네이티브 변경은 OTA로 내보낼 수 없다는 점을 팀 전체가 알고 있어야 한다. 그리고 OTA 페이로드도 스토어 정책 안에서 운영해야 한다. 앱의 핵심 목적을 바꾸는 변경은 금지다.
롤백 절차. 직전 정상 버전으로 되돌리는 방법을 문서로 남긴다. 장애 상황에서 문서 없이 기억에 의존하면 복구가 늦어진다.
베타 트랙. TestFlight나 Play의 내부·비공개 테스트 트랙을 거쳐 실사용자 손에 한 번은 쥐여보고 나간다. 내부 테스터가 못 찾는 버그를 외부 테스터가 하루 만에 찾는다.
모니터링. 출시일에는 대시보드를 띄워두고 크래시율, ANR, 핵심 퍼널을 실시간으로 본다. 롤아웃 확대 여부를 판단할 근거가 여기서 나온다.
빌드와 검증
__DEV__ 분기, console.log, 디버그 코드 제거 확인서명
스토어 정책
.so의 16KB 페이지 크기 호환 확인PrivacyInfo.xcprivacy와 사용 SDK 매니페스트 구비보안
성능과 안정성
네트워크와 운영
이 체크 리스트를 한 문장으로 줄이면 이렇다. 디버그에서 되는 것과 프로덕션에서 안 터지는 것은 다른 문제다.
검증은 릴리스 빌드로, 실기기에서, 완전 종료 후 재실행으로 한다. 출시 후를 볼 눈, 즉 크래시 리포팅과 모니터링을 먼저 붙이고 나서 단계적으로 내보낸다. 서명 키 백업과 강제 업데이트 게이트처럼 나중에 추가할 수 없는 항목은 첫 출시 전에 끝내둔다.
스토어 정책의 구체적인 날짜와 타깃 버전은 매년 갱신된다. 출시 직전에 Play Console과 App Store Connect의 현행 요구사항, 그리고 React Native 릴리스 노트를 한 번 더 확인하는 습관을 들이는 게 좋다.