프레임워크와 의존성 업그레이드: 무엇을 함께 바꾸고 어떻게 증명할 것인가
버전 숫자가 아니라 목표 호환 조합을 정하고, 변경 기록과 잠금 파일로 실제 차이를 읽으며, 고정 픽스처와 회귀 검증으로 한 경계씩 이동한다.
목차
표준·구현·측정·해석 표시는 무엇인가요?
- 표준웹 표준이나 언어 명세가 정한 동작
- 구현특정 기술이나 브라우저가 실제로 구현한 동작
- 측정명시한 환경에서 직접 실행해 관찰한 결과
- 해석앞선 근거에서 도출한 설계 판단
- 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용
30초 요약
프레임워크와 의존성 업그레이드는 버전 숫자 하나를 바꾸는 일이 아니다. 목표 버전들이 함께 동작할 수 있는지 호환성 표로 가설을 세우고, 변경 기록과 잠금 파일로 실제 차이를 읽은 뒤, 같은 픽스처와 회귀 검증으로 전후 동작을 비교하는 일이다.
- 호환성 표: 함께 쓸 현재·목표 버전과 그 조합의 근거를 적은 표
- 잠금 파일: 패키지 관리자가 실제로 선택한 직접·간접 의존성 버전을 고정한 파일
- 픽스처와 회귀 검증: 같은 입력·환경에서 기존의 중요한 동작이 유지되는지 전후로 비교하는 방법
반복해서 기억할 문장은 이것이다.
목표 조합을 적고, 한 호환 가설씩 바꾸고, 같은 증거로 비교한다.
현재 기준선 고정 → 업그레이드 목적·목표 조합 결정 → 공식 변경 기록 확인
→ 한 호환 단위 변경 → 선언·잠금 파일 차이 검토
→ 같은 픽스처로 정적 검사·빌드·동작·운영 신호 비교 → 단계적 배포“최신으로 전부 올리기”는 목표가 아니다. 지원 종료 회피, 보안 수정, 필요한 기능, 성능 개선처럼 왜 바꾸는지와 어떤 조합까지 갈지를 먼저 정해야 검증 범위와 중단 조건을 정할 수 있다.
이 글의 대표 상황은 **지원 중인 React Native·React·Expo 조합으로 이동하는 업그레이드 풀 리퀘스트(pull request, PR)**다. 설치 명령의 성공이 아니라 목표 조합, 잠금 파일의 실제 변화, Android·iOS 빌드와 기존 사용자 자료가 같은 검증을 통과하는지를 끝까지 확인한다.
업그레이드는 의존성 그래프와 실행환경을 바꾸는 일이다
보이는 변경: react-native 0.x → 0.y
함께 달라질 수 있는 것:
React·렌더러와 동료 의존성
Metro·Babel과 코드 변환 규칙
Android Gradle Plugin·Gradle·JDK
iOS 프로젝트·CocoaPods·Xcode 설정
네이티브 라이브러리·코드 생성
간접 패키지와 잠금 파일동료 의존성(peer dependency)은 패키지가 직접 설치해 숨기기보다 사용하는 애플리케이션이 함께 제공해야 한다고 선언한 호환 패키지 범위다. 경고를 억지로 무시하면 설치는 끝나도 런타임에 두 React 사본이 섞이거나 지원하지 않는 렌더러 조합이 될 수 있다.
따라서 업그레이드의 완료 조건은 “설치 명령이 0으로 종료됨”이 아니다. 목표 그래프가 예상대로 해석되고, 대상 실행환경에서 기존 계약과 새 목적이 함께 성립해야 한다.
버전 숫자는 위험 신호이지 호환성 증명이 아니다
SemVer가 답하는 질문: 게시자는 공개 API 변화의 크기를 어떻게 선언했는가?
앱 검증이 답하는 질문: 우리의 실제 조합과 사용 흐름이 계속 동작하는가?patch도 회귀를 만들 수 있고 major도 사용하지 않는 영역의 변경일 수 있다. 숫자는 조사 우선순위와 예상 변경 폭을 정하는 입력으로 쓰고, 최종 판단은 공식 변경 기록과 실행 증거로 한다.
최신 하나가 아니라 목표 호환 조합을 정한다
배포 태그(dist-tag)는 패키지 저장소가 특정 버전을 가리키도록 붙이고 나중에 다른 버전으로 옮길 수
있는 이름이다. 따라서 latest는 버전 번호 자체가 아니라 저장소 운영자가 움직일 수 있는 포인터다.
그러므로 “latest 설치”를 업그레이드 계획으로 삼지 않는다. 현재와 목표를 표로 적는다.
| 경계 | 현재 | 목표 | 공식 호환 근거 | 우리 직접 검증 |
|---|---|---|---|---|
| React·DOM 연결 계층 | 고정 ID | 목표 묶음 | 공식 이전 안내·동료 범위 | 렌더·브라우저 연결·오류 관측 |
| React Native | 고정 ID | 지원 버전 | 지원 현황·두 버전의 프로젝트 차이 | Android·iOS 빌드와 시작 |
| Expo SDK | 고정 ID | 다음 SDK | SDK 변경 기록·호환 패키지 | 구성 검사·네이티브 생성·실기기 흐름 |
| Node·패키지 관리자 | 고정 ID | 자동 검증과 같은 목표 | 각 도구 지원 정책 | 깨끗한 설치·운영 빌드 |
| 핵심 네이티브 모듈 | 고정 ID | 호환 후보 | 플랫폼·라이브러리 문서 | 권한·빌드 연결·백그라운드 흐름 |
고정 ID는 latest 같은 움직이는 이름이 아니라 재현 가능한 정확한 버전·잠금 파일 다이제스트다.
표의 “지원”과 “우리 검증”도 구분한다. 공식 지원 조합은 시험할 후보를 정하지만 우리 기능의 성공을
대신 증명하지 않는다.
이 예의 목적은 “항상 0.86으로 가라”가 아니다. 지원 상태는 날짜에 묶이고, 아직 지원 중이라는 사실도 앱의 라이브러리·운영체제 조합이 준비됐다는 뜻은 아니다.
한 패키지가 아니라 한 호환 가설씩 바꾼다
좋은 분리
A. React Native + 요구되는 React + 네이티브 프로젝트 변경
B. 상태 관리 라이브러리 major 변경
C. 테스트 러너 major 변경
나쁜 분리
React Native만 올려 의도적으로 지원하지 않는 React 조합을 잠시 만듦한 호환 가설씩 바꾼다는 말은 실패했을 때 원인을 한 묶음으로 좁힐 수 있게 한다는 뜻이다. 서로 독립된 프레임워크 major, 빌드 도구 교체와 대규모 애플리케이션 리팩터링을 한 변경에 섞지 않는다.
오래 밀린 업그레이드도 가능한 한 지원되는 중간 경계를 거친다. 단, 모든 라이브러리에 “무조건 한 minor씩”이라는 보편 규칙은 없다. 공식 migration guide가 직접 이동을 지원하는지, 중간 버전을 실행해야 자료 변환이나 경고를 볼 수 있는지로 경로를 정한다.
변경 기록은 해야 할 일과 위험 목록으로 바꾼다
release note는 한 출시의 변경 설명이고, changelog는 여러 출시의 변경 기록이며, migration guide는 기존 사용자가 새 계약으로 이동하는 절차다. 세 문서가 겹칠 수 있지만 같은 뜻은 아니다.
각 현재→목표 구간에서 다음을 추출한다.
- 제거되거나 사용 중단 예정인 API
- 기본값과 오류 처리처럼 코드가 컴파일돼도 달라지는 동작
- 필요한 codemod와 수동 수정
- TypeScript 타입과 JSX·변환 규칙
- 브라우저·Node·Android·iOS 최소 지원 버전
- 빌드 설정·환경 변수·생성 프로젝트 변경
- 자료 형식, 캐시와 배포·롤백 호환성
- 알려진 회귀와 목표 patch에서 수정된 문제
deprecation은 앞으로 제거될 API나 동작임을 미리 알리는 사용 중단 예고다. 경고가 있는 중간 버전을 통과하면 제거 전에 사용 지점을 찾을 수 있다.
이 역사적 경로를 모든 React 업그레이드의 영구 절차로 외우지 않는다. 대신 목표 구간에 경고를 제공하는 중간 버전, migration guide와 codemod가 있는지 매번 찾는 판단법을 가져간다.
선언 차이와 실제 의존성 그래프 차이를 함께 읽는다
package.json diff
framework: 1.4 → 1.5
lockfile diff에서 확인할 것
직접 패키지의 정확한 버전·무결성
새로 추가·제거된 간접 패키지
같은 패키지의 중복 버전
출처 URL·해시와 동료 의존성 해석
패키지 관리자·잠금 형식 변화로 생긴 전체 재작성 여부잠금 파일이 크다고 검토를 포기하지 않는다. 패키지 관리자 자체를 동시에 바꿨다면 형식 변화와 실제 그래프 변화를 분리한다. 의도하지 않은 저장소·Git 참조·major 전이가 보이면 원인을 찾고, 자동 업데이트가 고친 취약점도 실제로 어떤 버전이 선택됐는지 확인한다.
깨끗한 환경에서 잠금 파일을 고정하는 설치 명령으로 다시 설치한다. 개발자의 오래된
node_modules가 누락된 의존성을 우연히 제공하는 상황을 막고 CI와 같은 그래프를 시험하기 위해서다.
자동 변환은 초안이고 동작 증거가 필요하다
codemod는 코드의 특정 구문 패턴을 다른 패턴으로 일괄 변환하는 프로그램이다. 반복 수정을 줄이고 사람이 놓칠 사용 지점을 찾는 데 유용하지만, 제품의 의도를 이해하는 완성된 마이그레이션은 아니다.
자동 변환 뒤에는 다음을 한다.
- 변환 명령과 버전을 기록한다.
- 생성된 diff를 읽어 의미가 바뀐 지점을 찾는다.
- 포맷·정적 타입·lint로 기계적 오류를 잡는다.
- 같은 회귀 테스트와 픽스처에서 실제 동작을 확인한다.
- 필요하면 작은 수동 수정과 이유를 별도 커밋으로 남긴다.
자동 변환과 기능 리팩터링을 같은 diff에 섞지 않으면 리뷰어가 도구의 규칙적 변화와 사람의 판단을 구분하기 쉽다.
같은 픽스처와 증거로 변경 전후를 비교한다
하나의 거대한 데모보다 위험 경계별 작은 픽스처를 둔다.
| 픽스처 | 고정하는 것 | 잡으려는 회귀 |
|---|---|---|
| React 렌더 픽스처 | SSR HTML, hydration, Effect와 오류 경계 | 렌더 순서·불일치·오류 보고 변화 |
| 번들 픽스처 | 진입점·동적 import·환경 변수 | 모듈 해석·청크·tree shaking 변화 |
| RN 네이티브 픽스처 | Android·iOS 프로젝트와 핵심 모듈 | 링크·코드 생성·권한·시작 실패 |
| 자료 픽스처 | 이전 버전에서 만든 저장 자료 | schema·직렬화·캐시 비호환 |
| 관측 픽스처 | 고정 오류·source map·release ID | 오류 누락·중복·symbolication 실패 |
서버 측 렌더링(SSR)은 서버에서 React HTML을 먼저 만들고, hydration은 브라우저가 그 HTML에 상호작용을 연결하는 과정이다. tree shaking은 사용되지 않는 모듈 코드를 최종 산출물에서 제외하는 최적화다. symbolication은 압축·변환된 오류 위치를 사람이 읽는 원본 함수·파일·줄로 복원하는 과정이다.
기준선에서도 같은 검증을 먼저 실행한다. 업그레이드 전부터 실패하던 테스트를 새 회귀로 오해하지 않고, 새 버전에서만 달라진 결과를 좁힐 수 있다.
검증 계층은 위험에 맞춘다.
- 정적 타입·lint: 제거된 API와 타입 계약
- 단위·통합 테스트: 애플리케이션 로직과 프레임워크 경계
- 운영 빌드: 번들러·최적화·환경 변수와 source map
- 브라우저 E2E: hydration, 라우팅과 핵심 사용자 흐름
- Android·iOS 빌드와 실행: 네이티브 링크·권한·시작·백그라운드 동작
- 성능·산출물 비교: 시작 시간, 번들 크기, 메모리와 빌드 시간의 예산
모든 업그레이드에 모든 테스트를 기계적으로 돌리라는 뜻은 아니다. 변경한 경계에서 실패할 수 있는 동작을 최소 하나의 실행 증거가 지나가게 한다.
Web, React, React Native와 Expo의 현재 적용
Web에서는 브라우저·Node 지원 범위, 번들러, 서버에서 만든 HTML에 브라우저 상호작용을 연결하는 hydration과 캐시·산출물을 목표 조합에 함께 적는다. 깨끗한 운영 빌드와 실제 브라우저 핵심 흐름, 오래된 HTML·분할 파일 조합을 실행해 개발 서버에서 보이지 않는 회귀를 확인한다.
React는 목표 major의 upgrade guide, breaking change, 타입 변경과 codemod를 읽는다. react와
실제 렌더러인 react-dom 또는 React Native가 지원하는 조합을 확인하고, 렌더·hydration·Effect·
오류 관측 픽스처로 동작을 비교한다.
따라서 JavaScript 테스트만 초록색이어도 양 플랫폼의 운영 빌드와 시작을 생략하지 않는다. 사용하는 카메라·알림·파일·딥 링크·백그라운드 작업 같은 실제 네이티브 경계를 선택해 확인한다.
이 명령은 현재 Expo 제품의 편의 도구다. 보편 원리는 목표 호환 조합 정렬 → 구조 검사 → 네이티브 프로젝트 변경 → 공식 변경 기록 → 실제 동작 검증이다. 다른 프레임워크에서는 다른 명령으로 같은 질문에 답한다.
자주 실패하는 업그레이드
모든 패키지를 한 번에 최신으로 올린다
실패 원인을 어느 경계에 돌릴지 알기 어렵다. 서로 호환돼야 하는 묶음은 함께, 독립된 major와 도구 교체는 분리한다.
major만 위험하고 patch는 안전하다고 가정한다
SemVer는 게시자의 공개 API 선언이며 오표기와 앱 고유 조합까지 막지 못한다. 변경 기록과 회귀 증거를 본다.
설치 성공을 업그레이드 완료로 본다
패키지 해석만 끝났을 수 있다. 운영 빌드, 대상 런타임과 핵심 흐름을 실행한다.
잠금 파일을 생성 소음으로 버린다
간접 그래프와 출처 변화가 숨어 있다. 선언 diff와 잠금 diff를 함께 읽는다.
codemod diff에 기능 리팩터링을 섞는다
기계적 변환과 동작 변경을 구분하기 어려워진다. 변환, 검증과 의도적 개선의 경계를 나눈다.
깨끗한 새 설치만 시험한다
이전 버전이 만든 로컬 자료, 캐시와 오래된 네이티브 빌드 조합을 놓친다. 실제 전환 상태를 픽스처로 고정한다.
업그레이드 실행 순서
- 현재 정확한 버전·잠금 파일·도구·운영 빌드와 회귀 결과를 기준선으로 고정한다.
- 지원 종료, 보안, 기능이나 성능 중 업그레이드 목적과 성공 기준을 쓴다.
- 목표 호환성 표와 공식 지원·동료 범위·모르는 조합을 기록한다.
- 현재→목표의 release note, changelog와 migration guide에서 작업·위험 목록을 만든다.
- 서로 호환돼야 하는 최소 묶음 하나만 바꾸고 선언·잠금·생성 프로젝트 diff를 읽는다.
- 같은 픽스처에서 정적 검사, 깨끗한 설치, 운영 빌드와 위험 기반 회귀 검증을 실행한다.
- 산출물·성능·오류 신호를 기준선과 비교하고 실패하면 해당 호환 가설 안에서 원인을 좁힌다.
- 검증한 산출물을 점진 배포하고 실제 채택·회귀 신호를 본 뒤 다음 묶음으로 이동한다.
20편에서는 업그레이드 결과의 좋고 나쁨을 “빨라졌다”로만 말하지 않고 빌드 시간, 산출물 크기, 전송량과 관측 비용을 같은 단위와 경계로 비교하는 방법을 다룬다.
한 문장으로 다시 설명하기
프레임워크와 의존성 업그레이드는 최신 숫자를 설치하는 일이 아니라 목표 호환 조합을 정하고 한 호환 가설씩 실제 의존성 그래프와 실행환경을 바꾼 뒤, 같은 픽스처·회귀 검증·운영 신호로 기존 계약과 업그레이드 목적이 함께 성립함을 증명하는 일이다.
스스로 확인하기
- 왜 의존성 업그레이드는
package.json한 줄보다 넓은 변경인가? - 유의적 버전의 major·minor·patch는 무엇을 말하고 무엇을 증명하지 못하는가?
wanted,latest와 우리 팀의 목표 호환 조합은 어떻게 다른가?- 변경 범위를 작게 만든다는 말이 항상 패키지 하나씩 올린다는 뜻이 아닌 이유는 무엇인가?
- release note, changelog와 migration guide에서 어떤 위험·작업 목록을 뽑아야 하는가?
- 잠금 파일 diff가 실제 의존성 그래프의 중요한 증거인 이유는 무엇인가?
- 공식 codemod가 성공한 뒤에도 같은 픽스처와 회귀 검증이 필요한 이유는 무엇인가?
- React Native·Expo 업그레이드에서 JavaScript 검사 외에 어떤 네이티브 경계를 확인해야 하는가?
Active recall
기억에서 꺼내 보기
답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.
01유의적 버전의 minor나 patch 업데이트면 앱 검증 없이 안전하다고 볼 수 있을까?
정답
아니다. 숫자는 게시자가 선언한 공개 API 변화의 신호이며 앱의 빌드 설정, 간접 의존성과 실제 사용 동작의 호환성은 별도로 검증해야 한다.
관련 설명 다시 읽기왜 그런가
버전 규칙은 위험을 분류하는 입력이지 특정 애플리케이션의 테스트 결과가 아니다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
02패키지 관리자가 보여 주는 latest가 항상 지금 선택해야 할 목표 버전일까?
정답
아니다. latest는 저장소의 배포 태그이고 현재 범위가 허용하는 wanted, 프레임워크 지원 조합과 조직의 검증 목표는 서로 다르다.
관련 설명 다시 읽기왜 그런가
가장 새롭다는 사실과 현재 시스템에 지원되는 조합이라는 사실은 다르다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
03변경 범위를 줄이려면 React Native, React와 Expo를 반드시 한 패키지씩 따로 올려야 할까?
정답
아니다. 서로 특정 버전 조합을 요구하면 그 묶음이 최소 호환 단위다. 독립된 다른 도구 업그레이드는 분리해 원인을 좁힌다.
관련 설명 다시 읽기왜 그런가
작은 변경은 패키지 개수 1이 아니라 한 가지 호환 가설만 바꾸는 것이다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
04package.json에서 한 줄만 바뀌었다면 잠금 파일의 수백 줄 변경은 검토하지 않아도 될까?
정답
아니다. 잠금 파일은 실제로 선택된 직접·간접 의존성 그래프를 기록하므로 예상하지 않은 패키지·버전·출처 변경을 확인해야 한다.
관련 설명 다시 읽기왜 그런가
선언 한 줄이 간접 의존성 여러 개를 다시 해석할 수 있다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
05공식 codemod가 오류 없이 끝나면 런타임 회귀 검증도 끝난 것일까?
정답
아니다. codemod는 알려진 코드 형태를 기계적으로 바꾸지만 애플리케이션의 의미, 빌드 환경과 사용자 흐름이 유지되는지는 테스트해야 한다.
관련 설명 다시 읽기왜 그런가
구문 변환 성공과 실제 동작 보존은 다른 증거다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
출처와 검증 범위
아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.
- Semantic Versioning 2.0.0Semantic Versioning · 표준 · 확인 2026-08-04
- package-lock.jsonnpm Docs · 공식 문서 · 확인 2026-08-04
- npm-outdatednpm Docs · 공식 문서 · 확인 2026-08-04
- React 19 Upgrade GuideReact · 공식 문서 · 확인 2026-08-04
- Upgrading to New VersionsReact Native · 공식 문서 · 확인 2026-08-04
- Releases OverviewReact Native · 공식 문서 · 확인 2026-08-04
- Upgrade Expo SDKExpo · 공식 문서 · 확인 2026-08-04