문제 상황
React Native Expo로 릴리즈 빌드 시 다음과 같은 에러가 발생했다.
Warning: TypeError: queryString.stringify is not a function (it is undefined)
에러 메시지가 추상적이어서 뭐가 원인인지 감도 안 잡혔다.
node_modules나 package-lock.json을 지우고 다시 빌드해봐도 계속 실패했다.
원인 분석
며칠째 헤매다가 마침내 구글링을 통해 원인을 찾아냈다. 참고한 문서는 다음과 같다.
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의 호이스팅(가까운 버전 우선)이 의도치 않게 잘못된 버전을 끌어올 가능성이 있다.
추정 원인 정리
- 호이스팅으로 잘못된 버전이 주입됨
- (A가 기대하는 7.x 대신 9.x가 로드되거나, 반대로 9.x를 기대하는데 7.x가 로드됨)
- 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을 꼭 써볼 생각이다.
'프론트엔드 > TAVE-15기' 카테고리의 다른 글
| 웨더타고 - TAVE 15기 연합프로젝트 프론트엔드 회고 (2) | 2025.08.03 |
|---|---|
| [디자인 패턴] 플라이웨이트 패턴 (Flyweight Pattern) (0) | 2025.04.10 |
| [디자인 패턴] 팩토리 패턴(Factory Pattern) - 심플 팩토리, 등록형 팩토리, 팩토리 메소드 (0) | 2025.04.10 |
| [디자인 패턴] 싱글톤 패턴(Singleton Pattern) (0) | 2025.03.31 |