React Native 모노레포의 늪: Dual React 이슈를 해결하는 pnpm catalog 전략
1인 인디 해커로서 다수의 앱과 웹을 동시에 생산하는 SaaS Factory 시스템을 구축하다 보면, 기술적인 로직보다 더 까다로운 것이 바로 의존성(Dependency)의 흐름을 설계하는 일입니다. 최근 PMF Verify를 포함하여 StockAI, 투공(내 집 마련 투자공부) 등 여러 모바일 서비스를 하나의 모노레포 안에서 통합 관리하기 위해 시스템 개편을 진행했습니다.
하지만 공통 UI 컴포넌트들을 패키지로 분리하고 각 앱에 연결하는 과정에서, 제 개발 인생 5년 중 가장 난해했던 에러 세트를 만났습니다. 혹시 여러분도 TypeError: property is not writable 혹은 useMemoCache of null 같은 메시지 앞에서 멈춰 서 계신가요? 오늘은 이 지옥 같은 에러의 본질적인 원인과 pnpm catalog를 활용한 정석적인 해결법을 공유합니다.
안드로이드 에뮬레이터의 비명과 원인 분석
공용 UI 패키지인 @ui-native를 각 앱에 배포하고 빌드를 실행하자마자 시스템은 다음과 같은 런타임 에러를 뿜어냈습니다.
- TypeError: property is not writable
- TypeError: Cannot read property useMemoCache of null
- TypeError: Cannot read property default of undefined
경험이 많은 개발자라면 직감할 수 있습니다. 이것은 코드의 논리적 오류가 아니라 런타임 환경에서 두 개 이상의 서로 다른 엔진이 충돌하고 있다는 신호입니다. 원인은 바로 리액트 중복 로드(Dual React) 이슈였습니다. 모노레포 내의 개별 앱과 공용 패키지가 각각 자신의 node_modules 안에 react를 소유하면서, 메트로(Metro) 번들러가 패키지 경계를 넘을 때마다 서로 다른 리액트 인스턴스를 로드하게 된 것입니다.
React Native 엔진이 초기화될 때 전역 변수를 선언하는데, 이미 다른 인스턴스가 선언한 변수를 또 덮어쓰려다 보니 자바스크립트 엔진이 방어벽을 친 것이 property is not writable 에러의 실체입니다. 또한 리액트 훅(Hooks)은 전역 디스패처에 의존하므로, 서로 다른 엔진 사이에서 훅을 호출하면 연결 고리를 찾지 못해 null 참조 에러가 발생하게 됩니다.
해결 전략: peerDependencies를 통한 권한 위임
이 문제를 아키텍처적으로 해결하려면 공용 UI 패키지가 주도권을 내려놓아야 합니다. 자신이 리액트를 직접 소유(dependencies)하는 대신, 자신을 호출하는 호스트 앱의 리액트를 빌려 쓰겠다고 선언하는 peerDependencies 설정이 필요합니다.
공용 패키지의 package.json에서 dependencies를 비우고 이를 peerDependencies로 이동시켰습니다. 이렇게 하면 공용 패키지는 독자적인 리액트 엔진을 설치하지 않고, 호출하는 앱의 환경에 기생하게 됩니다. 결과적으로 전체 런타임에서 리액트 엔진은 단 하나만 존재하게 되어 충돌의 원인이 제거됩니다.
pnpm catalog: 버전 일관성을 유지하는 세련된 방법
여기서 한 단계 더 나아가 pnpm 9 버전부터 지원하는 catalog 기능을 도입했습니다. 모노레포 환경에서 각 패키지마다 버전을 일일이 명시하다 보면 버전 파편화가 발생하기 쉽습니다. 하지만 pnpm catalog를 쓰면 루트의 pnpm-workspace.yaml 파일에서 단 한 번만 버전을 정의하고 모든 패키지가 이를 공유할 수 있습니다.
예를 들어 catalog:mobile이라는 이름으로 리액트 버전을 고정해두면, 수십 개의 앱과 패키지가 항상 동일한 버전의 엔진 위에서 동작함을 보장할 수 있습니다. 이는 시스템의 예측 가능성을 높이고, 향후 OS 패치나 라이브러리 업데이트 시 발생할 수 있는 리스크를 획기적으로 줄여줍니다.
시스템 빌더로서 얻은 기술적 통찰
이번 삽질을 통해 다시 한번 깨달았습니다. 모노레포는 단순히 폴더를 모아놓은 바구니가 아니라, 의존성의 흐름을 설계하는 고도의 아키텍처라는 사실입니다. 1인 빌더에게 가장 중요한 자산은 시간이며, 그 시간을 지키기 위해서는 견고한 인프라가 선행되어야 합니다.
- 라이브러리는 가벼워야 합니다: 공용 패키지는 무거운 엔진을 직접 소유하지 말고 호스트 앱에 위임해야 생태계가 안정됩니다.
- 과감한 환경 초기화: 의존성 구조를 바꿨다면 기존의 node_modules와 락파일을 완전히 제거하고 다시 설치하는 결단력이 필요합니다.
- 도구에 대한 깊은 이해: pnpm의 호이스팅 정책과 Metro 번들러의 동작 방식을 파악하는 것이야말로 단순 코더를 넘어 시스템 아키텍트로 성장하는 지름길입니다.
마치며: 다시 가동되는 SaaS Factory
이제 다시 평화를 되찾은 저의 SaaS Factory는 활기차게 가동 중입니다. 멈췄던 StockAI의 차트 UI와 투공의 교육 화면들을 다시 기깔나게 그려나갈 예정입니다. 혹시 비슷한 에러로 고생 중인 빌더들이 있다면 지금 바로 package.json을 열어보세요. 시스템 내의 왕은 오직 한 명이어야 생태계가 유지됩니다.
인디 해커로서 다수의 서비스를 효율적으로 관리하기 위한 여정은 앞으로도 계속됩니다. 제 기록이 여러분의 소중한 시간을 아껴드렸길 바랍니다.