본문 바로가기
Q&A해결2026년 4월 25일1분 읽기

pnpm 10 workspace 내부 패키지가 Hot Reload 안 잡힙니다

모
모노레포담당
조회 761댓글 1

pnpm 10으로 monorepo를 운영 중입니다. apps/web (Vite + React) 에서 packages/ui 를 import해서 쓰는데, packages/ui 코드를 고쳐도 web의 Vite dev 서버가 reload를 안 합니다.

node_modules에 symlink는 잘 걸려있고 import도 잘 되는데, 변경 감지가 안 되네요. pnpm 9에서는 잘 됐었습니다.

댓글 1

김김영삼6개월 전

pnpm 10에서 linker 기본값이 'isolated'에서 'hoisted'에 가깝게 변경됐고, symlink chain이 한 단계 더 깊어졌습니다. Vite의 file watcher가 그 chain을 따라가지 못하는 게 보고하신 증상의 원인입니다.

핵심 진단

# symlink 구조 확인
ls -la apps/web/node_modules/@your/ui

# pnpm 9까지: @your/ui → ../../../packages/ui (직접)
# pnpm 10: @your/ui → ../../../node_modules/.pnpm/.../packages/ui (1단계 더 깊음)

해결 1) Vite watcher에 명시적으로 폴더 알려주기

// apps/web/vite.config.ts
import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
server: {
watch: {
// pnpm 10: symlink follow 강제
followSymlinks: true,
// 모노레포 다른 패키지 명시
ignored: ['!**/packages/**']
},
fs: {
// workspace 루트까지 접근 허용
allow: [path.resolve(__dirname, '../../')]
}
}
})

해결 2) preserveSymlinks 끄기 (권장)

// vite.config.ts
export default defineConfig({
resolve: {
preserveSymlinks: false, // 기본이지만 명시
},
})

이걸 true로 두면 symlink를 따라가지 않아서 watcher가 source를 못 찾습니다.

해결 3) packages/ui 자체의 빌드 모드 점검

가장 흔한 함정 — packages/ui가 컴파일된 dist만 export하면 source 변경은 reload 트리거가 안 됩니다.

// packages/ui/package.json
{
"name": "@your/ui",
"version": "0.0.0",
"main": "./src/index.ts", // ← src 직접 export (개발 시)
"types": "./src/index.ts",
"exports": {
".": {
"development": "./src/index.ts",
"default": "./dist/index.js"
}
},
"publishConfig": {
"main": "./dist/index.js"
}
}

또는 tsconfig로 path mapping:

// tsconfig.json
{
"compilerOptions": {
"paths": {
"@your/ui": ["../../packages/ui/src"]
}
}
}

해결 4) pnpm linker 설정 변경

# .npmrc (workspace 루트)
node-linker=isolated # pnpm 9 동작과 가장 가까움
shamefully-hoist=false
prefer-symlinked-executables=true

해결 5) Vite 7의 새 기능 — fs.cachedChecks

Vite 7부터 모노레포 파일 변경 감지가 개선됐습니다.

"devDependencies": {
"vite": "^7.0.0"
}

업그레이드만으로 일부 케이스는 자동 해결.

실무 체크리스트

  1. Vite dev 서버 재시작 (config 변경 시 필수)
  2. packages/ui의 main이 src를 직접 가리키는지 확인
  3. node_modules 청소 후 재설치 — rm -rf node_modules pnpm-lock.yaml && pnpm install
  4. Vite 7로 업그레이드
  5. packages/ui 자체 watch 설정 — tsup --watch 또는 vite build --watch

대안 — turborepo + pnpm

HMR 트리거를 packages 변경에서 web 변경으로 자동 cascade 시키려면:

// turbo.json
{
"tasks": {
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["^dev"]
}
}
}
// packages/ui/package.json
"scripts": {
"dev": "tsup --watch"
}

이 구조면 packages/ui가 watch 모드로 dist를 재빌드하고, web은 그 dist를 import하니 변경이 자연스럽게 전파됩니다.

마지막 한 줄

pnpm 10 + Vite 6 모노레포에서 HMR 문제는 거의 항상 (a) main이 dist를 가리킴 또는 (b) preserveSymlinks=true 둘 중 하나입니다. 위 설정 점검 후 안 되면 vite --debug 로 watcher 로그를 보면 정확한 무시 원인을 찍어줍니다.

Ctrl+Enter로 등록