React Native 기반 앱 프로젝트를 운영하면서, Store 심사 지연으로 인해 Hotfix 배포나 간단한 UI 수정조차 수일 이상 소요되는 일이 잦았습니다.

특히, 사용자(근로자)에게 주일 마다 피드백을 받아오는 상황에서 잦은 빈도로 업데이트되던 서비스였기 때문에, 이런 병목은 전체 업데이트 일정에 큰 영향을 끼쳤습니다.

이러한 문제를 해결하기 위해 CodePush 기반의 무중단 배포 환경을 구축했습니다.


왜 CodePush를 도입했는가

Microsoft에서 제공하는 CodePush는 React Native 앱의 JS 번들만 OTA(Over-the-Air) 방식으로 배포할 수 있도록 해주는 서비스입니다.

앱을 재설치하거나 스토어 심사를 거치지 않고도 즉각적인 업데이트가 가능한 구조입니다.

도입 이전에 겪던 문제들

  • 스토어 심사 지연 (평균 1~3일)
  • 간단한 문구 변경이나 UI 수정도 전체 빌드 필요
  • QA 이후 빠르게 반영할 수 없는 운영 속도

CodePush 배포 파이프라인

참고 image : ingg.dev

도입 구조 및 파이프라인 설계

CodePush 도입 후 다음과 같이 디버그–스테이징–릴리즈 3단계 배포 파이프라인을 구성했습니다:

CodePush 배포 파이프라인

각 단계는 다음의 역할을 가집니다:

단계설명
Debug개발 환경에서 테스트, 로컬 디바이스 반영
StagingQA 팀 검증용, 내부 그룹 대상 배포
Release실 사용자 대상 최종 배포 (스토어 심사 없이 실시간 반영)

CodePush는 앱에 포함된 JS 번들만 교체하므로, 네이티브 코드가 바뀌지 않는 한 무중단으로 배포가 가능합니다.


CodePush 배포 커맨드

CodePush는 App Center CLI를 통해 JS 번들을 OTA 방식으로 배포합니다.
Staging → QA → Production 흐름으로 운영했습니다.

  • Staging & promote 배포 예시
appcenter codepush release-react \
  -a your-org/your-app-android \
  -d Staging \
  --target-binary-version "1.0.0" \
  --description "버튼 색상 수정"

  appcenter codepush promote \
  -a your-org/your-app-android \
  -s Staging \
  -d Production \
  --description "Staging 안정화 → 운영 배포"

Native 연동 참고

CodePush는 JS만으로 작동하지 않으며, 네이티브 설정이 반드시 필요합니다.

  • iOS: AppDelegate.m, Info.plist
  • Android: MainApplication.java, build.gradle

📌 공식 가이드 참고: CodePush Setup Docs


실제 적용 코드

App.tsx

import CodePush, { DownloadProgress } from "react-native-code-push";

const App = () => {
  const [isUpdating, setIsUpdating] = useState(false);
  const [progress, setProgress] = useState(0);

  useEffect(() => {
    CodePush.sync(
      { installMode: CodePush.InstallMode.IMMEDIATE },
      undefined,
      ({ receivedBytes, totalBytes }: DownloadProgress) => {
        setProgress(Math.floor((receivedBytes / totalBytes) * 100));
      },
    );

    CodePush.checkForUpdate().then((update) => {
      setIsUpdating(!!update);
    });
  }, []);

  return (
    <Fragment>
      ...
      {isUpdating ? (
        <CodePushProgressScreen progress={progress} />
      ) : (
        <TemplateNavigation />
      )}
      ...
    </Fragment>
  );
};

export default CodePush({ checkFrequency: CodePush.CheckFrequency.MANUAL })(
  App,
);

커스텀 업데이트 화면 UI 구성 사용자에게 업데이트 상태를 명확히 안내하기 위해 진행률 표시 화면도 구성했습니다.

CodePushProgressScreen.tsx

const CodePushProgressScreen = ({ progress }) => (
  <ContainerView>
    ...
    <LogoWrap>
      <Logo source={require("./img/ic-symbol-logo.png")} />
      <StringLogo source={require("./img/ic-logo.png")} />
    </LogoWrap>
    <CodePushProgressText>{`업데이트를 진행합니다. 진행률 ${progress}%`}</CodePushProgressText>
    <ProgressBar>
      <InnerBar width={(progress * 188) / 100} />
    </ProgressBar>
    ...
  </ContainerView>
);
  • 진행률 바의 넓이는 receivedBytes 기준으로 계산
  • 진행 중인 상태를 명확히 안내하여 사용자 혼란 방지

CodePush 종료 예정 안내

Microsoft는 2025년 3월 31일부로 App Center 서비스 종료를 발표했습니다.
이에 따라 CodePush 기능도 포함되어 중단될 예정입니다.
자세한 내용은 공식 종료 안내를 참고하세요.

이후에는 다음과 같은 대안이 고려될 수 있습니다:

  • Expo OTA Updates
  • 자체 CDN 기반 JS 번들 배포 시스템 구축
  • App Store / Google Play 배포를 위한 Fastlane 기반 자동화 스크립트 구성

마무리하며

CodePush 도입은 단순한 라이브러리 추가를 넘어,
배포 구조 개선, 운영 효율성 향상, 그리고 사용자 경험 향상이라는 실질적인 효과를 만들어냈습니다.

특히, 스토어 심사 없이 JS 레이어만 업데이트 가능한 구조
빠르게 움직이는 서비스 환경에서 매우 유효한 선택이었습니다.

현재는 종료를 앞두고 있지만, 이 경험을 통해
OTA 업데이트의 원리와 UX 설계 포인트를 더 깊이 이해할 수 있었고,
향후 Expo, Fastlane, CDN 기반 배포 전략에도 적용할 기반이 되었습니다.