Skip to main content
네이티브
입문네이티브

앱 버전과 호환성 계약: 어떤 JavaScript가 어느 설치 앱에서 실행되는가

사용자용 앱 버전, 특정 바이너리의 빌드 식별자, JavaScript와 네이티브 계층을 묶는 런타임 버전을 분리해 호환되지 않는 업데이트 실행을 막는다.

마지막 검증 재검증 정책: 제품 버전 의존: 새 주요 버전마다 재검증
목차
표준·구현·측정·해석 표시는 무엇인가요?
  • 표준웹 표준이나 언어 명세가 정한 동작
  • 구현특정 기술이나 브라우저가 실제로 구현한 동작
  • 측정명시한 환경에서 직접 실행해 관찰한 결과
  • 해석앞선 근거에서 도출한 설계 판단
  • 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용

30초 요약

React Native 앱은 스토어나 내부 배포로 설치되는 네이티브 바이너리(네이티브 코드·설정을 담은 실행 파일 묶음)와, 그 위에서 바꿔 실행할 수 있는 JavaScript·자산(이미지·글꼴 같은 파일) 계층을 함께 가진다. 그래서 버전 하나로 모든 정체성과 호환성을 표현하기 어렵다.

먼저 답하는 질문답하지 못하는 것
앱 버전사용자가 보는 어느 출시인가?같은 출시 안의 정확한 빌드 반복
빌드 식별자어느 네이티브 바이너리 반복인가?다른 빌드와 같은 업데이트를 실행해도 되는가?
런타임 버전어느 네이티브 계약과 업데이트가 한 호환 집합인가?계약 내용이 실제로 같은지 자동 증명
업데이트 식별자어느 JavaScript·자산 결과물을 실행하는가?설치 바이너리의 네이티브 기능

반복해서 기억할 문장은 이것이다.

앱 버전은 출시를, 빌드 식별자는 바이너리를, 런타임 버전은 호환 집합을 가리킨다.

네이티브 경계 확인 → 호환성 표식 결정 → 새 바이너리 빌드
→ 같은 계약의 업데이트 생성 → 실행 조합 테스트·관찰

런타임 버전 문자열을 맞추는 일은 마지막 표현이다. 먼저 JavaScript가 어떤 네이티브 기능을 요구하고, 설치 바이너리가 무엇을 제공하는지 판단해야 한다.

이 글을 관통하는 상황: 바코드 스캔 변경은 원격 업데이트로 보낼 수 있을까

React Native 화면에 바코드 스캔 버튼을 추가하는 변경을 맡았다고 하자. JavaScript에는 Camera.scanBarcode() 호출을 추가했고, 새 네이티브 카메라 구현을 포함한 로컬 개발 빌드에서는 정상 동작했다. 이제 풀 리퀘스트와 출시 과정에서 답해야 할 질문은 이것이다.

이 변경은 기존 설치 앱에 스토어 재설치 없이 JavaScript를 보내는 무선 업데이트 (Over-the-Air Update, OTA)로 보낼 수 있는가, 아니면 새 바이너리를 먼저 배포해야 하는가?

기존 설치 바이너리 B1은 Camera.open()만 제공하고 런타임 버전 camera-v1에 속한다고 하자. 새 JavaScript 업데이트 U2는 Camera.scanBarcode()를 요구한다. U2를 실수로 camera-v1 대상으로 게시하면 문자열 비교에서는 B1의 후보가 될 수 있지만, 사용자가 스캔 버튼을 누르는 순간 B1에는 호출할 네이티브 함수가 없어 실패할 수 있다. TypeScript 검사와 새 개발 빌드의 성공만으로는 이미 설치된 B1과 U2의 조합을 검증하지 못한 것이다.

잘못된 경로
  PR에서 새 네이티브 함수 추가
  → 새 개발 빌드에서만 확인
  → U2를 기존 camera-v1에 OTA 게시
  → B1이 U2를 선택
  → 스캔 버튼에서 없는 네이티브 함수 호출
 
올바른 경로
  네이티브 계약 변경으로 분류
  → Camera.scanBarcode()를 제공하는 B2 빌드
  → 새 runtimeVersion camera-v2
  → B2를 설치 가능한 경로로 배포
  → U2는 camera-v2만 대상으로 게시
  → B2+U2 정상 실행과 B1+U2 제외를 함께 확인

반대로 기존 네이티브 기능만 사용하는 화면 문구·스타일·화면 상태 계산 수정은 같은 계약 안의 OTA 후보가 될 수 있다. 두 변경의 차이는 파일이 JavaScript인지가 아니라 기존 바이너리가 새 JavaScript의 요구 기능을 이미 제공하는지다.

뒤의 각 절은 이 상황을 같은 식별자로 다시 본다. 앱 버전은 출시 이름을, B1·B2의 빌드 식별자는 설치된 네이티브 반복을, camera-v1·camera-v2는 호환 집합을, U1·U2는 실제 JavaScript·자산 결과물을 가리킨다. 마지막에는 B2+U2가 동작하는지만 보지 않고 B1이 U2를 받지 않는지와 운영 오류에서 이 실행 조합을 구분할 수 있는지도 확인한다.

설치 앱에는 바뀌는 속도가 다른 두 계층이 있다

설치된 앱 바이너리
  ├─ 네이티브 계층
  │   ├─ React Native·Expo 네이티브 코드
  │   ├─ JavaScript에 플랫폼 기능을 제공하는 네이티브 연결 코드와 컴포넌트
  │   ├─ 권한·앱 설정·플랫폼 자원
  │   └─ JavaScript 실행환경
  └─ 업데이트 계층
      ├─ JavaScript 번들
      └─ 이미지·글꼴 같은 자산

업데이트 계층이 항상 자유롭게 바뀐다는 뜻은 아니다. 새 JavaScript가 기존 네이티브 계층이 모르는 함수·자료형·설정을 요구하면 함께 실행할 수 없다.

Web에도 브라우저 지원 범위, 서버 기능 계약과 캐시된 파일의 호환성 문제가 있다. React Native에는 여기에 사용자가 스토어에서 새 버전을 설치하기 전까지 남아 있는 네이티브 바이너리라는 경계가 추가된다. 서버에 새 JavaScript를 게시했다는 사실만으로 모든 사용자의 네이티브 계층이 바뀌지 않는다.

앱 버전과 빌드 식별자는 답하는 질문이 다르다

예를 들어 다음 두 iOS 빌드는 사용자에게 모두 3.2.0으로 보일 수 있다.

com.example.app / app 3.2.0 / build 45
com.example.app / app 3.2.0 / build 46

빌드 45와 46은 네이티브 의존성이나 설정이 다를 수 있다. 따라서 오류 보고에 3.2.0만 남기면 어느 바이너리에서 생겼는지 모른다.

반대로 빌드 식별자가 다르다고 항상 네이티브 계약도 다른 것은 아니다. 코드 변경 없이 다시 서명하거나 빌드 파이프라인만 고쳐 새 빌드를 만들 수도 있다. 빌드 정체성과 호환성 집합을 같은 뜻으로 고정하지 않는다.

호환성은 JavaScript가 요구하고 바이너리가 제공하는 기능의 관계다

수식처럼 쓰면 다음과 같다. 는 왼쪽의 모든 항목이 오른쪽에 포함된다는 뜻이다.

업데이트가 요구하는 네이티브 기능 ⊆ 설치 바이너리가 제공하는 네이티브 기능

앞의 바코드 스캔 상황을 기능 집합으로 다시 쓰면 다음과 같다.

설치 바이너리 B1이 제공
  Camera.open()
  Storage.get(key)
 
업데이트 U1이 요구
  Camera.open()
  → 실행 가능
 
업데이트 U2가 요구
  Camera.scanBarcode()
  → B1에 함수가 없다면 실행 불가

함수 이름만 계약은 아니다. 인자와 반환 자료형, 네이티브 컴포넌트의 속성·이벤트, 권한과 플랫폼 설정, React Native·Expo 개발 도구 묶음(SDK)과 JavaScript 실행환경도 결과에 영향을 줄 수 있다.

순수한 문구·스타일 변경처럼 기존 네이티브 기능만 사용하는 JavaScript 변경은 같은 계약 안에 머물 수 있다. 새 네이티브 라이브러리를 추가하거나 기존 네이티브 연결 명세를 바꾸면 새 계약으로 보는 것이 안전하다.

런타임 버전은 호환 집합의 표식이다

설치 빌드 B
  platform = ios
  runtimeVersion = "camera-v2"
 
원격 업데이트 U
  platform = ios
  runtimeVersion = "camera-v2"
 
후보 조건
  B.platform == U.platform
  B.runtimeVersion == U.runtimeVersion

채널·게시 시각 같은 다른 전달 조건은 15편에서 다룬다. 이 글에서는 네이티브 호환성 후보를 나누는 플랫폼과 런타임 버전에 집중한다.

런타임 버전은 호환성 검사기가 아니다

실제 계약
  오래된 B0: Camera.scanBarcode() 없음
  새로운 U2: Camera.scanBarcode() 필요
 
잘못된 표식
  B0.runtimeVersion = "1"
  U2.runtimeVersion = "1"
 
문자열 비교 결과: 후보
실제 실행 결과: 함수가 없어 실패 가능

따라서 런타임 버전이 “호환성을 보장한다”는 말은 네이티브 계약 변화에 맞춰 값이나 자동 정책을 정확히 갱신했다는 조건을 포함한다. 같은 문자열은 검사 결과가 아니라 팀이 같은 계약이라고 선언한 표식이다.

반대 오류도 있다. 계약이 같은데 런타임 버전을 매번 다르게 만들면 안전 쪽으로 격리되지만, 실제로 호환되는 이전 빌드에 수정 업데이트를 전달할 수 없고 지원할 집합이 불필요하게 늘어난다.

어떤 변경이 호환성 경계를 바꾸는지 먼저 본다

다음 질문 중 하나라도 “예”라면 새 네이티브 계약을 검토한다.

  • JavaScript에 플랫폼 기능을 제공하는 네이티브 연결 코드나 컴포넌트를 추가·삭제·업그레이드했는가?
  • JavaScript가 호출하는 네이티브 메서드·인자·반환 자료형이 바뀌었는가?
  • 권한, 앱 설정, 연결된 플랫폼 자원이나 플러그인이 바뀌었는가?
  • React Native·Expo 개발 도구 묶음(SDK), JavaScript 엔진이나 JavaScript·네이티브 연결 구조 설정이 바뀌었는가?
  • 기존 설치 빌드에는 없는 파일·기능을 JavaScript가 전제로 하는가?

반대로 TypeScript 파일을 바꿨다는 이유만으로 계약이 반드시 바뀌지는 않는다. 기존 네이티브 기능만 사용하는 React 컴포넌트의 상태 계산·문구·스타일 수정은 같은 계약일 수 있다. 의존성 잠금 파일이 바뀌어도 순수 JavaScript 패키지만 달라졌는지 네이티브 코드가 함께 달라졌는지 구분한다.

런타임 정책은 운영 규칙과 함께 고른다

정책은 안전성 하나로 순위를 매기지 않고 출시 흐름과 실패 조건을 함께 본다.

정책호환 집합이 바뀌는 때장점놓치기 쉬운 조건
직접 관리팀이 문자열을 바꿀 때계약 그룹을 명시적으로 공유·분리 가능네이티브 변경 뒤 올리는 일을 잊을 수 있음
appVersion앱 버전을 올릴 때공개 출시 단위와 단순하게 맞음네이티브 변경인데 앱 버전을 안 올리면 위험
nativeVersion앱 버전 또는 빌드 식별자가 바뀔 때빌드별 격리가 쉬움네이티브 빌드 번호를 수동 관리해야 하며 EAS 원격 앱 버전 소스와 함께 쓸 수 없음
fingerprint지문 입력이 바뀔 때수동 누락 가능성을 줄임현재 실험적이며 입력 범위·불필요한 분리를 검증해야 함

appVersion 정책이 맞으려면 “네이티브 계약이 바뀌는 모든 공개 빌드에서 앱 버전을 올린다”는 운영 규칙이 필요하다. 내부 테스트 빌드를 같은 앱 버전으로 반복하면서 네이티브 코드가 달라진다면 그 정책만으로 두 계약을 구분하지 못할 수 있다.

nativeVersion 정책은 현재 EAS가 서버에서 앱 버전과 빌드 번호를 관리하는 원격 버전 소스와 함께 지원되지 않는다. 비슷한 출시 단위가 필요하면 Expo가 안내하는 appVersion 정책을 검토한다. 소스 설정에서 빌드 번호를 자동 증가시키는 운영과 혼동하지 말고, 실제 생성된 값과 조합 지원 여부를 현재 문서에서 확인한다.

fingerprint는 호환 가능성을 직접 실행해 증명하는 기능이 아니다. 어떤 프로젝트 입력이 네이티브 런타임에 영향을 줄 수 있는지 계산해 지문으로 묶는 현재 도구 정책이다. 실험 상태와 계산 입력을 확인한 뒤 선택한다.

플랫폼별 계약이 다르면 표식도 나눈다

하나의 JavaScript 저장소를 쓰더라도 네이티브 기능은 플랫폼마다 다를 수 있다.

iOS runtime = "payments-ios-v3"
  → Apple Pay native contract v3
 
Android runtime = "payments-android-v2"
  → Google Pay native contract v2

두 플랫폼의 계약이 실제로 같고 같은 출시 규칙을 따른다면 공통 값을 쓸 수 있다. 한쪽에만 네이티브 변경이 있는데 숫자를 보기 좋게 맞추기 위해 다른 쪽 계약까지 불필요하게 끊을 필요는 없다. 반대로 값 하나로 묶었다면 두 플랫폼 모두 그 계약을 지킨다는 검증이 필요하다.

검증은 소스 설정이 아니라 만들어진 조합을 대상으로 한다

지원하는 각 플랫폼·런타임 집합에 최소 검증표를 둔다.

설치 빌드업데이트기대 결과확인할 것
같은 플랫폼·같은 런타임기존 네이티브 기능만 요구실행 후보·정상 동작양성 검증: 일어나야 할 주요 화면과 네이티브 호출이 동작함
같은 플랫폼·다른 런타임새 네이티브 기능 요구후보에서 제외음성 검증: 잘못 내려받거나 실행하지 않음
다른 플랫폼·같은 문자열플랫폼 전용 업데이트후보에서 제외음성 검증: 플랫폼 필터가 적용됨
같은 런타임으로 묶은 여러 빌드같은 업데이트모두 정상 동작제공 기능이 실제로 동일함

문자열 비교 테스트만으로 끝내지 않는다. 같은 런타임으로 묶은 가장 오래된 지원 빌드에서도 새 업데이트가 기존 네이티브 메서드와 자료형을 지키는지 실행한다. 새 계약은 실제 배포와 가까운 릴리스 빌드 또는 개발 빌드에서 확인한다.

일어나야 할 동작을 확인하는 양성 검증과 의도적으로 호환되지 않는 조합을 확인하는 음성 검증을 함께 둔다. 오래된 런타임이 새 업데이트를 받지 않는다는 “일어나지 않아야 할 일을 막는 검사”가 있어야 잘못된 대상 지정이 드러난다.

관측과 재현에는 실행 조합을 기록한다

{
  "platform": "ios",
  "appVersion": "3.2.0",
  "buildIdentifier": "46",
  "runtimeVersion": "payments-ios-v3",
  "updateId": "update-sha256-or-immutable-id"
}

업데이트 ID는 사용하는 시스템이 제공하는 바뀌지 않는 식별자나 콘텐츠 다이제스트를 쓴다. “현재 production”이나 “latest”처럼 시간이 지나면 다른 대상을 가리키는 이름만 남기지 않는다. 11편의 산출물 동일성 원칙과 같은 이유다.

오류가 특정 런타임에서만 늘었는지, 같은 런타임의 특정 빌드에서만 생겼는지, 같은 빌드의 새 JavaScript 업데이트부터 생겼는지를 각각 나눠 볼 수 있어야 한다.

모든 빌드 + 특정 updateId 실패
  → JavaScript 업데이트 후보
 
특정 buildIdentifier만 실패
  → 네이티브 빌드·설정 후보
 
특정 runtimeVersion 조합에서만 실패
  → 호환성 계약·대상 지정 후보

자주 실패하는 설계

앱 버전 하나로 모든 것을 식별한다

같은 앱 버전 아래 여러 네이티브 빌드와 JavaScript 업데이트가 있을 수 있다. 사용자용 출시, 바이너리 반복, 호환 집합과 실행 업데이트를 분리한다.

네이티브 의존성을 바꾸고 런타임 버전을 유지한다

문자열은 같아도 기존 바이너리에 함수가 없을 수 있다. 네이티브 경계 변경 검사를 빌드·게시 절차에 넣고 새 계약이면 새 바이너리를 먼저 만든다.

모든 빌드에 무조건 새 런타임 버전을 준다

안전 쪽 격리처럼 보이지만 호환되는 빌드에도 같은 수정 업데이트를 전달하지 못할 수 있다. 빌드 정체성과 계약 변화가 항상 같은지 출시 전략으로 결정한다.

채널이 호환성 검사를 대신한다고 생각한다

production·staging 같은 채널은 업데이트가 이동할 운영 경로다. 채널을 바꿔도 설치 앱에 없는 네이티브 기능이 생기지 않는다. 플랫폼·런타임 계약 검사를 별도로 유지한다.

설정 파일 값만 보고 실제 빌드를 확인하지 않는다

네이티브 프로젝트 값, 지원되는 정책의 자동 증가와 빌드 과정이 최종 값을 바꿀 수 있다. 단, nativeVersion과 EAS 원격 앱 버전 소스처럼 함께 지원되지 않는 조합도 있다. 만들어진 빌드와 게시된 업데이트에서 실제 플랫폼·앱 버전·빌드 식별자·런타임 버전을 읽어 대조한다.

호환성 계약을 바꾸는 판단 순서

  1. JavaScript 변경이 새 네이티브 함수·자료형·설정을 요구하는지 적는다.
  2. 지원 중인 각 플랫폼의 설치 바이너리가 그 기능을 제공하는지 확인한다.
  3. 계약이 바뀌면 런타임 버전 정책에 따라 새 호환 집합을 만들고 새 빌드를 생성한다.
  4. 원격 업데이트가 정확한 플랫폼·런타임 버전을 대상으로 하는지 산출물에서 확인한다.
  5. 호환되는 가장 오래된 빌드의 정상 실행과 호환되지 않는 빌드의 제외를 함께 검증한다.
  6. 운영에서 네이티브 빌드·런타임·업데이트 식별자를 기록하고 오류율을 조합별로 본다.

숫자를 언제 올릴지부터 시작하지 않는다. 어떤 계약이 달라졌고 어떤 설치 앱이 새 JavaScript를 실행할 수 있는지를 먼저 답하면 필요한 식별자가 따라온다.

한 문장으로 다시 설명하기

React Native 앱의 사용자용 앱 버전은 출시를, 빌드 식별자는 특정 네이티브 바이너리를, 런타임 버전은 JavaScript 업데이트와 네이티브 계약의 호환 집합을 가리킨다. 업데이트가 요구하는 네이티브 기능이 설치 바이너리에 있어야 실제로 호환되며, 같은 런타임 문자열은 이 사실을 자동 검사한 증명이 아니다. 네이티브 경계가 바뀌면 새 계약과 빌드를 만들고, 플랫폼·빌드·런타임· 업데이트 조합을 실제 산출물에서 검증하고 관측한다.

스스로 확인하기

  1. 앱 버전과 빌드 식별자는 각각 어떤 질문에 답하는가?
  2. 빌드 식별자가 다르다고 런타임 계약도 반드시 다르지는 않은 이유는 무엇인가?
  3. JavaScript 업데이트가 설치 바이너리와 호환되는 근본 조건은 무엇인가?
  4. runtimeVersion 문자열이 같은데도 실행이 실패할 수 있는 이유는 무엇인가?
  5. appVersion, nativeVersion, fingerprint 정책은 호환 집합을 언제 나누는가?
  6. 같은 런타임으로 묶은 여러 빌드에서 어떤 양성·음성 검증이 필요한가?
  7. 오류 재현을 위해 앱 버전 외에 어떤 실행 조합을 기록해야 하는가?

Active recall

기억에서 꺼내 보기

답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.

  1. 01앱 화면에 `3.2.0`이 보이면 어떤 네이티브 바이너리 반복이 설치됐는지까지 고유하게 알 수 있을까?

    정답

    아니다. 사용자용 앱 버전과 특정 빌드 반복을 가리키는 Android versionCode 또는 Apple CFBundleVersion을 함께 봐야 한다.

    왜 그런가

    같은 공개 앱 버전 아래 여러 빌드를 만들 수 있으므로 두 식별자는 답하는 질문이 다르다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  2. 02설치 앱과 원격 업데이트의 runtimeVersion 문자열이 같으면 실제 네이티브 기능도 자동 검사돼 반드시 호환될까?

    정답

    아니다. Expo Updates는 같은 플랫폼과 런타임 버전을 호환 후보로 고른다. 네이티브 계약이 바뀌었는데 값을 그대로 두면 문자열은 같아도 실행 중 실패할 수 있다.

    왜 그런가

    런타임 버전은 팀이나 자동 정책이 계약 변화를 정확히 반영해야 하는 선언 표식이다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  3. 03JavaScript가 새 네이티브 카메라 함수를 호출하도록 바뀌면 원격 JavaScript 업데이트만 보내도 될까?

    정답

    기존 설치 바이너리에 그 함수가 없다면 안 된다. 네이티브 계약을 포함한 새 빌드와 새 호환성 그룹이 먼저 필요하다.

    왜 그런가

    원격 업데이트는 이미 설치된 네이티브 계층에 새 네이티브 코드를 추가하지 못한다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  4. 04Expo의 appVersion 런타임 정책을 쓰면 네이티브 변경 뒤 앱 버전을 올리지 않아도 자동으로 새 호환성 그룹이 생길까?

    정답

    아니다. appVersion 정책은 앱 버전 값을 런타임 버전으로 사용하므로 네이티브 계약이 바뀔 때 앱 버전도 올리는 운영 규칙이 지켜져야 한다.

    왜 그런가

    자동 파생 정책도 입력값과 팀의 출시 규칙이 정확해야 한다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  5. 05원격 업데이트 뒤 React Native 오류를 재현하려면 사용자용 앱 버전 하나만 기록하면 충분할까?

    정답

    부족하다. 플랫폼, 앱 버전과 빌드 식별자, 런타임 버전, 실제 실행한 JavaScript 업데이트 식별자를 함께 기록해야 한다.

    왜 그런가

    같은 앱 버전에서도 네이티브 빌드와 실행 중인 업데이트 조합이 달라질 수 있다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

출처와 검증 범위

아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.

  1. Runtime versions and updatesExpo · 공식 문서 · 확인 2026-08-04
  2. Using EAS UpdateExpo · 공식 문서 · 확인 2026-08-04
  3. UpdatesExpo · 공식 문서 · 확인 2026-08-04
  4. EAS Update DebuggingExpo · 공식 문서 · 확인 2026-08-04
  5. Deploy updatesExpo · 공식 문서 · 확인 2026-08-04
  6. App version managementExpo · 공식 문서 · 확인 2026-08-04
  7. Native ModulesReact Native · 공식 문서 · 확인 2026-08-04
  8. Version your appAndroid Developers · 공식 문서 · 확인 2026-08-04
  9. CFBundleShortVersionStringApple Developer · 공식 문서 · 확인 2026-08-04
  10. CFBundleVersionApple Developer · 공식 문서 · 확인 2026-08-04
  11. App informationApple Developer · 공식 문서 · 확인 2026-08-04
이 문서의 마지막까지 읽었습니다.