들어가기 전에
Expo Updates(EAS Update)는 네이티브 빌드 없이 JavaScript/에셋 변경 사항을 앱에 바로 배포할 수 있는 OTA(Over-the-air) 업데이트 기능입니다.
가격 정책부터 적용 가능 범위, runtimeVersion, 채널, 브랜치, 롤백, 점진적 배포까지 개념을 정리해 보겠습니다.
가격 정책
EAS Update는 사용량 기반 요금제로 운영됩니다. 플랜별로 월간 활성 사용자(MAU), 글로벌 대역폭, 스토리지 한도가 다르고, End-to-end code signing은 Production 플랜부터 지원합니다.
Expo Updates 가능 여부
OTA로 배포할 수 있는 범위는 정해져 있습니다. 네이티브 코드가 필요 없는 변경만 OTA로 내려줄 수 있고, 네이티브 코드나 앱 권한이 바뀌면 반드시 새 바이너리를 빌드해서 스토어에 올려야 합니다.
가능
- JavaScript/TypeScript 코드
- 이미지 (import, require)
- 다만 이상한 조합의 다이나믹 require 문자열은 지원되지 않습니다
- json (import, require)
- 폰트 (useFonts, loadAsync)
- audio/video (import, require)
- 순수 JS로만 작성된 라이브러리 교체 (예: lodash, date-fns)
- 코드의
process.env.EXPO_PUBLIC_*- 빌드 시점에 값으로 치환되어 번들에 포함됩니다
process.env['EXPO_PUBLIC_KEY'],const { EXPO_PUBLIC_X } = process.env형태만 지원되고, 그 외 형태는 지원되지 않습니다
불가능
- 네이티브 코드/네이티브 의존성 변경
- 네이티브 코드가 들어있는 라이브러리 변경
- 앱 권한 변경
expo-font,expo-assetapp.json,app.config.ts- Expo Modules
.env
이런 변경이 필요하면 OTA가 아니라 EAS Build로 새 바이너리를 만들어서 스토어에 제출해야 합니다.
업데이트 가능한 조건
OTA 업데이트가 실제로 적용되려면 몇 가지 조건이 맞아야 합니다.
-
runtimeVersion이 일치해야 합니다 -
platform이 일치해야 합니다 (Android, iOS) -
빌드의
channel이 올바른branch에 연결되어 있어야 합니다
동작 방식은 대략 이렇습니다.
- 빌드에는
channel이 들어 있습니다. - 서버는 그
channel에 연결된branch를 찾습니다. - 그
branch안의 업데이트들 중에서platform이 같고runtimeVersion이 같은 업데이트만 호환되고- 그중 가장 최신 compatible update를 내려줍니다.
런타임버전 (runtimeVersion)
- https://docs.expo.dev/versions/latest/config/app/#runtimeversion
- https://docs.expo.dev/eas-update/runtime-versions/
- https://docs.expo.dev/versions/latest/sdk/updates/#automatic-configuration-using-runtime-version-policies
runtimeVersion은 네이티브 코드와 JS 번들이 서로 호환되는지 판단하는 기준값입니다. runtimeVersion을 설정한 채로 prebuild를 하면 네이티브 쪽에 값이 기록됩니다.
- Android:
strings.xml파일의expo_runtime_version - iOS:
Expo.plist파일의EXUpdatesRuntimeVersion
Configuration (app.json / app.config.js)
Manual: 문자열로 직접 지정합니다.
{
"expo": {
"runtimeVersion": "수동 지정"
}
}Automatic: 정책에 따라 자동으로 계산됩니다.
appVersion (기본 추천값): 앱 버전을 그대로 사용합니다.
// 1.0.0
{
"expo": {
"runtimeVersion": {
"policy": "appVersion"
},
"version": "1.0.0"
}
}nativeVersion: 앱 버전과 빌드 번호(iOS buildNumber, Android versionCode)를 조합해서 사용합니다.
// 1.0.0(1)
{
"expo": {
"runtimeVersion": {
"policy": "nativeVersion"
},
"version": "1.0.0",
"ios": {
"buildNumber": "1"
},
"android": {
"versionCode": 1
}
}
}fingerprint: 프로젝트 상태를 해시해서 runtime을 자동으로 계산해 줍니다.
{
"expo": {
"runtimeVersion": {
"policy": "fingerprint"
}
}
}채널 (channel)
채널은 이 빌드가 어느 업데이트 라인을 볼지 정하는 이름입니다. 빌드 시점에 네이티브 코드에 값이 들어가고, 브랜치와 연결되어 서로 다른 빌드에 서로 다른 업데이트를 제공할 수 있습니다.
EAS Build 사용 시
EAS Build를 사용 중이라면 eas update:configure 명령이 eas.json의 preview/production 프로필에 channel 속성을 자동으로 설정해 줍니다. 프로필 이름을 다르게 쓴다면 수동으로 설정해야 합니다.
// eas.json 파일 프로필에 channel 설정
{
"build": {
"preview": {
"channel": "preview"
// ...
},
"production": {
"channel": "production"
// ...
}
}
}EAS Build 사용하지 않을 때
EAS Build를 사용하지 않는다면(CNG 사용 여부에 따라) app.json이나 네이티브 프로젝트에서 채널을 직접 설정해야 합니다. 다른 환경을 위해 빌드를 만들 때는 올바른 채널에서 업데이트를 받아오도록 채널을 수정해야 합니다.
// app.json, app.config.js
{
"expo": {
// ...
"updates": {
// ...
"requestHeaders": {
"expo-channel-name": "your-channel-name"
}
// ...
}
}
}브랜치 (branch)
- https://docs.expo.dev/eas-update/eas-cli/
- https://docs.expo.dev/eas-update/debug/#verify-the-channelbranch-mapping
브랜치는 업데이트들이 순서대로 쌓이는 목록입니다. Git 브랜치가 커밋 목록인 것처럼, EAS Update 브랜치는 update 목록이라고 생각하면 됩니다.
채널이 어떤 브랜치를 가리킬지는 나중에 바꿀 수 있습니다.
eas channel:edit production --branch release-1.0채널에 연결된 브랜치 안에서 호환되는(runtimeVersion, platform이 맞는) 업데이트 중 가장 최신 것을 받게 됩니다.
롤백 (rollback)
문제가 생긴 업데이트를 되돌리는 방법은 두 가지입니다. 이전에 게시했던 업데이트로 돌리거나, 빌드에 내장된 업데이트로 돌릴 수 있습니다.
eas update:rollback점진적 배포 (rollouts)
한 번에 전체 사용자에게 업데이트를 배포하는 대신, 점진적으로 배포 범위를 넓히는 방법입니다. 두 가지 방식이 있습니다.
Per-update
버그 수정을 조심스럽게 배포할 때 사용합니다.
# 10%만 배포
eas update --rollout-percentage 10
# 10%에서 자동으로 100%로 늘어나지는 않음
# 필요하면 다시 100으로 설정
# --rollout-percentage를 지정하지 않으면 기본값은 100Branch-based
브랜치 단위로 점진적으로 전환하는 방식입니다.
eas channel:rollout
# 터미널에서 인터랙티브 가이드로 채널 선택, 롤아웃 지점 선택, 롤아웃 사용자 비율 설정을 도와줌앱에 적용 방법
- https://docs.expo.dev/versions/latest/sdk/updates/
- https://docs.expo.dev/versions/latest/config/app/#checkautomatically
- https://docs.expo.dev/eas-update/download-updates/
기본적으로 expo-updates는 앱 실행 시 업데이트를 확인합니다. 업데이트가 있으면 다운로드해두고, 다음에 앱을 재시작할 때 적용합니다. 이 시작 동작은 checkAutomatically, fallbackToCacheTimeout 옵션으로 조정할 수 있습니다.
// app.config.js
{
"expo": {
"updates": {
"checkAutomatically": "NEVER"
}
}
}ON_LOAD: 앱이 로드될 때마다 자동으로 업데이트를 확인 (기본값)ON_ERROR_RECOVERY: 평소에는 자동 확인을 하지 않고, 에러 복구 상황일 때만 자동으로 확인합니다. 자동 업데이트는 거의 끄고 싶지만 비정상 상태 복구는 살리고 싶을 때 쓰는 값입니다.WIFI_ONLY: 앱 로드 시 자동 확인은 하되, Wi-Fi일 때만 확인NEVER: 자동 확인을 안 함.checkForUpdateAsync()함수로 수동으로 확인
수동으로 확인하고 적용하는 코드는 이렇게 작성합니다.
import * as Updates from "expo-updates";
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
// 업데이트 안내 다이얼로그
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}앱 실행 흐름에서 가장 먼저 실행하는 게 좋을 것 같습니다.
정리
Expo Updates(EAS Update)는 네이티브 빌드 없이 JS/에셋 변경 사항을 빠르게 배포할 수 있는 좋은 도구지만, 아무 변경이나 OTA로 내려줄 수 있는 건 아닙니다. runtimeVersion, channel, branch가 어떻게 맞물려서 업데이트를 결정하는지 이해하고 나서 적용하는 게 중요해 보입니다.