프로젝트들에 모노레포를 적용하면서 했던 의사결정과 pnpm에 대한 이모저모
모노레포 적용이 필요했던 배경
- 멀티 레포로 관리되던 상황이었어서 공통의 util / 디자인 컴포넌트를 사용하기에 생각보다 큰 리소스가 예상되었다
- 비즈니스의 흐름을 이해하기 어렵다
- 피쳐 성격상 a웹과 b웹에서 같이 작업이 이루어지는 경우가 있을 수 있다
- 이때 pr등에서 확인하기가 어렵고 여러 ide 창을 켜놔야 하는 경우도 있었다
=> 모노레포를 적용하고 이를 통해 위의 상황을 개선해보자!!
그래서 선택지는 총 3개
- nx
- pnpm
- yarn berry
기존의 프로젝트 들은 yarn classic을 쓰고 있었다
처음 시도했던 것은 nx, 그러나
- nx는 꽤나 어려웠다
- 고점은 높아보였지만 러닝커브가 쉽지 않아 보였다
- 회사 스테이지상 학습을 위한 시간을 확보하기는 매우 어렵다 <- 개인의 희생을 강요할 수 없다
이제 선택지는 yarn berry와 pnpm만 남았다
어찌할 것인가
pnpm을 선택하기로 했다
- pnpm을 다뤄봤던 팀원이 있었고
- npm trends를 믿어보기로 했다

(이 당시 잘못 알고 있었던 것은 yarn berry는 node_modules가 존재하지 않아 rn에서 쓸 수 없을까 였었다.
yarn berry에서도 hoisted를 통해 node_modules를 생성할 수 있다)
pnpm은 빠르다
왜 빠른가
- 설치시 pnpm store에 해당 라이브러리 들을 설치한다
- a 프로젝트에서 라이브러리를 설치시 store로 부터 하드링크를 연결한다 <- 이미 설치되어 있다면 재설치를 하지 않는다
- node_modules/.pnpm에 store에서 가져온 라이브러리들이 존재한다
- 비슷한 개념이 심링크 이다
- 심링크는 바로가기로 연결만 한다
- 이 심링크를 사용하는 것은 아래에서 확인하자
node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ ├── bar -> <store>
│ └── qar -> ../../qar@2.0.0/node_modules/qar <- /node_modules/.pnpm/qar@2.0.0/node_modules를 심링크
├── foo@1.0.0
│ └── node_modules
│ ├── foo -> <store>
│ ├── bar -> ../../bar@1.0.0/node_modules/bar <- /node_modules/.pnpm/bar@1.0.0/node_modules를 심링크
│ └── qar -> ../../qar@2.0.0/node_modules/qar <- /node_modules/.pnpm/qar@2.0.0/node_modules를 심링크
└── qar@2.0.0
└── node_modules
└── qar -> <store>
1. 프로젝트에서 foo@1.0.0 라이브러리를 추가한다
2. node_modules/.pnpm에 foo@1.0.0이 하드링크로 추가된다 (이때 원본은 .pnpm의 store에 위치한다)
* package를 import하는 방법을 정할 수 있는데 가장 권장되는 수단은 COW(CopyOnWrite) file system 이다
* COW는 기본적으로 주소값 복사를 하고 변경이 필요한 경우 변경점만 별도로 디스크를 할당한다
3. foo@1.0.0의 내부서 의존하는 bar@1.0.0 / qar@2.0.0이 .pnpm에 하드링크로 추가된다
4. foo@1.0.0의 내부 라이브러리들은 각 .pnpm에 설치된 라이브러리들에 심링크로 연결되어 있다
5. foo@1.0.0은 프로젝트에서 직접 참조되므로 .pnpm의 외부로 이동된다. 이때 .pnpm을 심링크로 연결한다

(npm은 캐싱 없이 매번 설치마다 node_modules에서 디스크 공간을 차지해야 한다)
그 외에 장점은
- 유령 의존성을 없앤다
- 패키지 별로 node_modules를 가지고 버전 명세가 명확하다
- hoisting이 되지 않는 구조에서 package.json에 명세 없이 코드상에서 import를 하는 것은 실행이 되지 않는다
유령 의존성이란?
내 프로젝트에서 쓰고 있지만 package.json에는 없는 애
node_modules/
lodash/
a/package.json
b/package.json/lodash
패키지 a의 코드에서
import lodash from 'lodash' <- 요 코드를 실행할 수 있다 (npm과 yarn classic에서는 실행가능)
=> 런타임에서 에러발생이 가능한 환경이 된다
rn에서 pnpm을 사용할 때 hoisting을 사용해야 한다
왜냐하면 rn의 하위 버전에서는 symlink가 동작하지 않고 메트로 + 기존 라이브러리에서 node_modules의 계층형 구조를 인식하지 못한다
빌드시 매번 node_modules에 라이브러리가 존재하지 않는다는 에러를 마주할 수 있다
아래와 같이 rn 하위버전 metro 설정에서 override를 해줘야 할 수 도 있다
metro.config.js
const defaultConfig = getDefaultConfig(projectRoot);
const {
resolver: { sourceExts, assetExts },
} = defaultConfig;
function resolveFromProject(modulePath) {
return path.dirname(require.resolve(`${modulePath}/package.json`, { paths: [projectRoot] }));
}
const reactDir = resolveFromProject('react');
const config = {
resolver: {
extraNodeModules: {
react: reactDir,
},
},
};
module.exports = mergeConfig(defaultConfig, config);
but
store를 통한 의존성 설치시의 캐싱활용은 장점이 될 수 있다
유령의존성 제거 장점을 취하지 못한 것은 아쉬울 수 있다
ci/cd에서의 장점은 .pnpm store를 github에서 캐싱해둠으로써 매번 새로 설치하지 않아도 되는 장점을 이룰 수 있다
(아래는 캐싱 예시)
- name: Cache pnpm store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
.npmrc에서
node-linker를 hoisted로 설정할지 옵션을 설정할 수 있다
모노레포 프로젝트들의 버전을 override해줄 수 있다
이때 각 프로젝트의 node_modules에 라이브러리들이 설치가 된다
patch는 npm 환경에서 node_modules 내부 라이브러리를 직접 바꾸고 싶은경우 사용되는 방식이다
pnpm에서는 아래와 같이 설정할 수 있다
package.json
"pnpm": {
"overrides": {
"a>react": "19.0.0",
"react": "18.2.0",
},
"patchedDependencies": {
"c": "patches/c.patch",
"d": "patches/d.patch",
}
},
root/
├─ apps/
│ ├─ web/
│ └─ admin/
├─ packages/
│ ├─ ui/ (Shared Component Library)
│ ├─ utils/ (Shared Business Logic)
│ └─ config/ (ESLint, TSconfig shared)
└─ pnpm-workspace.yaml
* apps는 실제 서비스이다
* packages는 공유 라이브러리 또는 컴포넌트 들이다
* pnpm은 내부 패키지에 대해서는 store를 거이지 않는다.
* pnpm은 내부 패키지들을 softlink 처리한다
* packages를 수정하면 바로 반영된다
pnpm의 선택은 단순 속도 문제는 아니었고
프로젝트 구조, 개발 경험(DX), 라이브러리의 선택 기준등
생각보다 많은 것이 엮인 결정이었다.
'Frontend > React' 카테고리의 다른 글
| Redux persist와 migration (0) | 2025.11.27 |
|---|---|
| [번역] React Atomic하게 바라보기 (0) | 2024.05.19 |
| React patterns 🤔 (0) | 2023.06.06 |
| React 공식문서 주요개념 살펴보기 (1) | 2022.05.26 |
| React what is JSX? (번역글) 🤔 (2) | 2022.04.27 |