| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- 항해플러스
- 알고리즘
- 백준
- 리팩토링
- 탐욕알고리즘
- greedy
- 코딩테스트
- 리액트
- 클린코드
- 프로그래머스
- EC2
- SSE적용방법
- SSE 적용 방법
- 항해
- next.js
- react natve
- react
- 테스트코드
- JavaScript
- 프론트엔드
- SSE 후기
- SSE
- hot-updeter
- 2025년회고
- 그리디
- 항해99
- 코테
- jQuery
- 자바스크립트
- 회고록
- Today
- Total
공부 및 일상기록
[React Native] Hot Updater를 활용한 OTA 적용 본문
최근 회사 앱에 업데이트가 굉장히 잦았던 적이 있었다.
그때마다 빌드 후 심사 요청과 심사의 과정을 거쳐 심사가 완료되기까지의 시간이 걸려야 유저들에게 적용이 되었다.
잦은 업데이트로 인해 버그도 속출했는데, 버그를 금방 수정해주지 못하는게 좀 아쉬움이 남았다.
그래서 Code Push가 필요하다고 생각이 들어서 적용하려고 했지만.. Code Push는 지원이 더이상 되지 않기 때문에 차선택이 있는지 찾아보니, Hot updater라는 라이브러리가 있었고, 생각보다 단순한 구조라서 빠르게 적용할 수 있을것 같아 이 라이브러리를 사용하기로 생각했다.
OTA란?
Over the Air Upadate의 약자이고, 앱스토어/플레이스토어에 새로운 바이너리를 올리지 않고, 네트워크를 통해 앱 일부를 업데이트하는 개념이다.
React Native에서는 JS Bundle, 이미지/폰트 등의 assets파일, 일부 설정값 등을 변경할 수 있다.
하지만 네이티브 코드의 변경은 OTA로 변경하지 못한다.
쉽게 생각해서 JS코드로만 이뤄진 부분을 변경하는데 사용한다고 생각하는게 편하다!
CodePush란?
마이크로소프트가 App Center에서 제공했던 리액트네이티브 OTA 솔루션 이름이다.
구조는 대략 다음과 같다.
RN 앱
↓
현재 binary version 확인
↓
CodePush 서버에 업데이트 확인
↓
새 JS bundle 다운로드
↓
다음 실행 또는 즉시 reload
↓
새 bundle 실행
예전에는 리액트네이티브 개발에서 OTA하면 CodePush가 가장 유명했고, 나 역시도 코드푸쉬를 사용했었다.
하지만.. 2025년 3월 31일 서비스 지원이 중단되었고, 그래서 위 코드푸쉬를 클론해서 사용하거나 스스로 구축해서 사용하는 회사들이 생겨나기 시작했다.
Hot Updater란?
리액트네이티브에서 사용 가능한 OTA 솔루션이다. 아직 1버전도 나오지 않은 초기 라이브러리이다.
역할 자체는 코드푸쉬와 비슷하다.
차이가 있다면 Hot updater가 특정 SaaS에 종속된다기 보다 내가 원하는 구성으로 인프라를 직접 구성하고 스토리지를 붙이기 좋게 되어있다는 것이다.
나는 다음과 같이 구성했다.
서버
앱 서버가 돌아가는 EC2에 Node로 OTA서버를 하나 더 배포했다.
스토리지
S3를 사용했다.
DB
Postgre로 OTA용 DB를 따로 만들었다.
보통 cloudflare와 supabase를 사용하는 방식으로 공식 홈페이지에서도 예시를 드는데, 나는 이미 돌아가는 EC2가 있고, AWS가 좀 더 친숙하기 때문에 위와같은 구성을 하기로 생각했다.
구성 과정
1. 앱에 Hot updater SDK 설치하기
npm install @hot-updater/react-native@0.36.0
npm install -D hot-updater@0.36.0 @hot-updater/bare@0.36.0 \
@hot-updater/aws@0.36.0 @hot-updater/standalone@0.36.0
먼저 내 RN 프로젝트에 hot updater의존성들을 설치해줬다.
iOS에서는 Pods를 설치하고 Release에서 Hotupdater.bundleURL()로 bundle을 선택하게 연결했다.
Android도 HotUpdater.getJSBundleFile(...)을 통해 다운로드한 bundle을 선택하도록 네이티브 로더를 연결했다.
2. 게시용 설정 구성
RN 프로젝트에 hot-updater.config.ts에서 세 부분을 연결시켰다.
- build: bare 프로젝트의 index.js에서 Hermes bundle 생성
- storage: S3에 파일 업로드
- database: 자체 OTA 서버의 관리 API로 배포 정보 등록
import { s3Storage } from '@hot-updater/aws';
import { bare } from '@hot-updater/bare';
import { standaloneRepository } from '@hot-updater/standalone';
import { config } from 'dotenv';
import { defineConfig } from 'hot-updater';
config({ path: '.env.hotupdater' });
/** CLI 실행에 필요한 환경 변수를 검증하고 공백이 제거된 값을 반환한다. */
const requireEnvironmentVariable = (name: string): string => {
const value = process.env[name]?.trim();
// 설정이 빠진 상태에서는 번들 생성이나 게시를 시작하지 않는다.
if (!value) {
throw new Error(`[hot-updater] ${name} 환경 변수가 필요합니다.`);
}
return value;
};
const repositoryBaseUrl = requireEnvironmentVariable(
'HOT_UPDATER_REPOSITORY_BASE_URL',
);
const authToken = requireEnvironmentVariable('HOT_UPDATER_AUTH_TOKEN');
const region = requireEnvironmentVariable('HOT_UPDATER_S3_REGION');
const bucketName = requireEnvironmentVariable('HOT_UPDATER_S3_BUCKET_NAME');
const basePath = process.env.HOT_UPDATER_S3_BASE_PATH?.trim() || 'hot-updater';
export default defineConfig({
build: bare({
enableHermes: true,
entryFile: 'index.js',
sourcemap: true,
}),
storage: s3Storage({
region,
bucketName,
basePath,
}),
database: standaloneRepository({
baseUrl: repositoryBaseUrl,
commonHeaders: {
Authorization: `Bearer ${authToken}`,
},
}),
updateStrategy: 'appVersion',
});
3. DB구성과 서버 준비
서버는 Node.js, Express, Typescript, Hot updater pakage를 사용했고, DB는 PostgreSQL을 사용했다.
OTA서버는 앱 버전, 플랫폼, 채널에 알맞는 업데이트를 찾고, 다운로드 주소를 전달하는 역할을 한다.
DB는 어떤 앱에 어떤 업데이트를 제공할지 결정하는 배포 정보를 저장한다.
S3에는 실제 자바스크립트 번들 혹은 에셋 파일등을 보관한다. (이게 다운로드 주소다)
실제 업데이트는 어떻게 이뤄지나?
일단 두 부분을 나눠서 생각해야한다.
게시와 앱 확인
게시는 개발자가 코드를 수정하고 업데이트가 존재한다는 것을 DB에 게시하는것을 의미
앱 확인은 앱이 OTA서버를 통해서 DB에 새로 적용할 업데이트가 있는지 확인한다는 의미
게시할 때 CLI는 플랫폼에 맞게 JS와 asset을 묶고 Hermes bytecode를 생성한다. 생성된 번들을 S3에 업로드하고, 그 다음 OTA서버에 bundle ID, 대상 버전, 채널과 파일 위치등을 등록한다. 그 등록정보가 DB에 기록되는 것이다.
주의할점은 공통 JS를 쓰더라도 iOS와 Android는 플랫폼 분기, asset 처리, 빌드 결과가 달라질 수 있다. 따라서 하나의 bundle을 양쪽에 그대로 배포하는것이 아니라, 플랫폼별로 각각 생성해서 S3에 업로드 해줘야 한다.
앱이 확인할 땐, OTA서버에 최신 정보를 확인하고, 현재 내 앱의 버전과 bundle ID 등을 모두 비교하여 다운로드 할지 안할지 결정한다. 다운로드 대상이라면, S3를 통해 bundle을 다운받고 설치 후 앱을 재시작 하게 된다.
시행착오 1. 배포가 되었는데 앱에서 조회하면 500에러..
앱에서 업데이트를 조회하면 'Could not load credentials from any providers'라는 메시지와 함께 500에러가 발생했다.
로컬에서는 AWS Profile이 있었지만, EC2에서는 IAM Role이 연결되어 있지 않아서 발생했던 문제였다.
게시자가 S3에 업로드한다는 것과 사용자가 서버를 통해 앱용 다운로드 URL을 발급할 수 있는 것은 별개의 문제였던 것이다.
따라서 이는 EC2에 Role을 연결하고 필요한 S3 읽기 권한을 부여한 뒤 해결 되었다.
시행착오 2.안드로이드 APK는 통과했는데, iOS TestFlight에서는 OTA가 실패한다..?
안드로이드에선 간단하게 APK를 통해 릴리즈 버전을 테스트했고, iOS는 테스트플라이트를 사용하려고 했지만, 계속 실패했다.
그런데 알고보니 이건 실패가 아니라 정상 동작이였다.
테스트 플라이트로 배포하기 위해 3.0.7의 버전에서 3.0.8로 버전을 한단계 올렸는데, OTA 게시 명령어에서는 3.0.7로 올렸던 것이다.
그래서 분명 번들의 차이가 존재하지만 더 최신버전인 TestFlight앱을 신뢰하고 번들업데이트를 하지 않은 것이다.
이를 통해 오히려 앱 버전을 통한 제어도 잘 된다는것을 확인하게 되었다.
마치며
Hot updater는 사실 굉장히 직관적이고 단순한 방식을 사용하기 때문에 어려울건 없다. 공식문서도 잘 나와있어서 잘 찾아보면 답을 쉽게 찾을 수 있다.
다만 약간 아쉬운 점은 아직 정식버전이 출시되지 않은 점이다. 그리고 2025년에 처음 출시되어 커뮤니티 문서도 많지 않아서 오직 공식문서에 의존해야하는것이 좀 아쉬웠다. (공식문서가 좋다고 생각하지만 나는 커뮤니티를 보면 좀 더 이해가 빨리 간다고 생각하는 편이다.)
그래서.. 조금이라도 누군가 이 글로 도움을 받을 수 있다면 좋겠다는 생각이 들어서 아주 축약했지만 적용했던 이번 사례를 작성해 보았다.
'개발 > React' 카테고리의 다른 글
| [React] 무거워진 요청에 SSE를 적용해보자 (프론트엔드편) (0) | 2025.12.08 |
|---|---|
| EC2에 배포한 React가 사용자에게 항상 최신이 보이도록 설정하기 (0) | 2025.11.14 |
| [React] 성능 최적화 하기 (0) | 2025.09.15 |
| [WIL] 프론트엔드 테스트코드 - 8주차 과제 (2) | 2025.08.30 |
| [WIL] 프론트엔드에서 테스트코드 (1) | 2025.08.30 |