프론트엔드/TAVE-15기

[React Native] npm 패키지 의존성 충돌(query-string) 해결하기

icems0428 2025. 9. 4. 02:29

문제 상황

React Native Expo로 릴리즈 빌드 시 다음과 같은 에러가 발생했다.

Warning: TypeError: queryString.stringify is not a function (it is undefined)

 

에러 메시지가 추상적이어서 뭐가 원인인지 감도 안 잡혔다.

node_modules나 package-lock.json을 지우고 다시 빌드해봐도 계속 실패했다.


원인 분석

며칠째 헤매다가 마침내 구글링을 통해 원인을 찾아냈다. 참고한 문서는 다음과 같다.

https://stackoverflow.com/questions/79653811/typeerror-querystring-stringify-is-not-a-function-in-expo-router-tab-layout-w

 

TypeError: queryString.stringify is not a function in Expo Router tab layout – why does installing query-string fix it?

Warning: TypeError: queryString.stringify is not a function (it is undefined) This error is located at: Call Stack TabLayout(./(tabs)/_layout.tsx) (<anonymous>) ScreenContentWrapper (<

stackoverflow.com

 

이 글을 보고 package-lock.json 파일을 확인해보았더니 query-string 의존성이 서로 다른 버전으로 설치되어 있었다.

// package-lock.json 일부
"node_modules/@react-native-kakao/core": {
  ...
  "dependencies": {
    "crypto-js": "^4.2.0",
    "query-string": "^9.0.0", // 9.0.0 버전
    "return-fetch": "^0.4.5"
  }
}

// package-lock.json 일부
"node_modules/@react-navigation/core": {
  ...
  "dependencies": {
    "@react-navigation/routers": "^7.4.1",
    "escape-string-regexp": "^4.0.0",
    "nanoid": "^3.3.11",
    "query-string": "^7.1.3", // 7.1.3 버전
    "react-is": "^19.1.0",
    "use-latest-callback": "^0.2.4",
    "use-sync-external-store": "^1.5.0"
  }
}

 

내가 사용한 패키지 매니저는 npm이었으며,

npm은 의존성 버전이 충돌했을 때 플래튼(flatten) 방식이라는 것을 쓴다고 한다.

  • 동일한 버전이면 → 한 번만 설치해서 루트에 올림
  • 다른 버전이면 → 어쩔 수 없이 하위 node_modules에 따로 설치

그래서, 이론적으로는 7.x와 9.x가 공존하더라도 각자 자기 버전을 써야 정상이다.

하지만 React Native 빌드 도중에는 얘기가 다르다. Metro 번들러가 Node.js처럼 모듈을 완벽히 해석하지 못한다.

그 결과:

  • 특정 import가 루트 버전을 바라보거나,
  • ESM/CJS export 방식 차이 때문에 default/named export가 안 맞아서

결국 undefined 에러가 터질 수 있다.

즉, npm의 호이스팅(가까운 버전 우선)이 의도치 않게 잘못된 버전을 끌어올 가능성이 있다.

 

추정 원인 정리

  1. 호이스팅으로 잘못된 버전이 주입됨
    • (A가 기대하는 7.x 대신 9.x가 로드되거나, 반대로 9.x를 기대하는데 7.x가 로드됨)
  2. ESM/CJS export 차이
    • query-string 7.x와 9.x는 export 방식이 조금 달라서, default import / named import가 꼬이면 undefined 발생

해결 방법

이럴 때는 각 패키지가 원하는 버전대로 정확히 설치되게 강제하는 게 가장 안정적이다.

나 같은 경우, Stack Overflow에서 본 방법을 그대로 따라 했다.
package.json에 overrides를 추가해서 query-string 버전을 덮어쓴 것이다.

// package.json
"overrides": {
  "query-string": "7.1.3"
}

 

혹은 이렇게 패키지별 버전을 지정할 수도 있다.

// package.json
{
  "overrides": {
    "@react-navigation/core>query-string": "7.1.3",
    "@react-native-kakao/core>query-string": "9.0.0"
  }
}

 

첫 번째 방식(단일 버전 강제)은 간단하지만, 다른 패키지가 깨질 수 있다는 단점이 있다.
두 번째 방식(패키지별 강제)은 더 안전하다.


✅ 최종 정리

  • 에러 원인: query-string이 서로 다른 버전으로 설치됐고, Metro가 잘못 해석하면서 undefined 발생
  • 해결 방법: package.json에 overrides 추가해서 버전 충돌 강제 해결
  • 교훈: 릴리즈 빌드에서만 나는 추상적 에러는 의존성 버전 충돌을 먼저 의심하자

✅ 추가

npm은 호이스팅 때문에 라이브러리 버전이 뒤섞일 수 있는데, pnpm은 구조가 다르다.

  • pnpm은 누가 어떤 라이브러리를 쓰는지 명확히 구분한다.
  • 각 패키지의 의존성을 따로 격리해 설치하기 때문에 이런 충돌이 애초에 잘 안 생긴다.
  • 게다가, npm은 프로젝트마다 라이브러리를 통째로 설치하지만, pnpm은 글로벌 스토리지에 한 번만 설치하고 프로젝트에서는 심볼릭 링크로 가져다 쓴다.
    → 덕분에 설치 속도도 빠르고 디스크 효율도 훨씬 좋다.

그래서 다음 프로젝트에서는 pnpm을 꼭 써볼 생각이다.