블로그 목록
apps

EAS Update로 앱 OTA 업데이트하기 — 스토어 심사 없이 JS 코드 바로 배포

앱을 출시하고 나서 가장 답답했던 게 이거다. 오타 하나, 색깔 한 줄 고치려고 해도 앱스토어 심사를 다시 받아야 한다. 길면 며칠씩 걸린다. 급한 버그는 더 미칠 노릇이다.

그런데 RN 앱은 결국 JS 번들이 본체다. 네이티브 코드를 안 건드리는 수정이라면, 번들만 새로 내려주면 된다. 이게 OTA(Over-The-Air) 업데이트고, Expo에서는 EAS Update가 그 역할을 한다.

세팅

기존 Expo 프로젝트라면 명령어 한 번이면 된다.

eas update:configure

이러면 app.jsonupdates.url이 자동으로 들어가고, runtimeVersion 정책이 잡힌다. 여기서 runtimeVersion이 핵심이다. 네이티브 코드의 "버전"을 가리키는 값인데, 이게 같아야만 OTA 번들이 호환된다고 판단한다.

{
  "expo": {
    "runtimeVersion": { "policy": "appVersion" },
    "updates": { "url": "https://u.expo.dev/your-project-id" }
  }
}

배포

빌드는 채널(channel)에 연결되고, 업데이트는 브랜치(branch)로 올라간다. 그냥 git 브랜치명에 맞춰 쏘면 된다.

eas update --branch production --message "결제 버튼 오타 수정"

20~30초면 끝난다. 이미 설치된 앱들은 다음 실행 때 새 번들을 받아간다. 심사 없음. 즉시 반영.

언제 안 되는지가 더 중요하다

처음에 제일 헷갈렸던 부분. OTA는 JS와 에셋만 갈아끼운다. 다음 경우는 절대 OTA로 안 된다. 새 빌드를 올려야 한다.

  • 새 네이티브 라이브러리 추가 (expo-camera 등 설치)
  • app.json의 네이티브 설정 변경 (권한, 아이콘, 스플래시)
  • Expo SDK 버전 업그레이드

이걸 모르고 네이티브 의존성을 추가한 채 eas update만 쏘면, 구버전 앱은 업데이트를 무시한다. runtimeVersion이 안 맞으니 호환되는 빌드만 받는 안전장치가 작동한 거다. 헷갈렸지만 사실 이게 앱이 깨지는 걸 막아주는 거였다.

업데이트 적용 시점 다루기

기본값은 "다음 콜드 스타트 때 적용"이다. 사용자가 앱을 껐다 켜야 보인다는 뜻. 더 빨리 적용하고 싶으면 코드에서 직접 제어한다.

import * as Updates from 'expo-updates';

async function checkUpdate() {
  const update = await Updates.checkForUpdateAsync();
  if (update.isAvailable) {
    await Updates.fetchUpdateAsync();
    await Updates.reloadAsync(); // 즉시 재시작
  }
}

다만 앱 켜자마자 강제 리로드하면 사용자 입장에선 화면이 한 번 깜빡인다. 나는 "다음 실행 때 조용히 반영"을 기본으로 두고, 핫픽스가 급할 때만 강제 리로드를 쓰는 쪽으로 정리했다.


정리하면 EAS Update는 만능이 아니라 "JS 레벨 수정용 지름길"이다. 네이티브를 안 건드리는 버그·문구·로직 수정은 OTA로 즉시, 네이티브가 얽히면 정식 빌드로 — 이 경계만 명확히 두면 출시 후 운영이 훨씬 가벼워진다.