pnpm workspace 를 사용할 때 React 버전 이슈

2025. 07. 07.

배경

pnpm workspace 를 사용하는데, 다른 react 버전을 사용하는 두 개 이상의 package 를 포함할 경우 react 를 사용하는 프로젝트가 하나일 땐 발생하지 않던 type check 에러가 발생했다.

현상

  1. pnpm workspace + apps/web (next 15.3.4 + react ^19.0.0) 에서는 next dev, next build 가 멀쩡하다.

    project_repo
    ├── apps
    │   └── web
    │       ├── package.json  // next 15, react 19, etc.
    │       └── ...
    ├── package.json
    ├── pnpm-workspace.yaml  // packages: apps/*
    └── ...
    
    전체 folder structure
    .
    ├── apps
    │   └── web
    │       ├── README.md
    │       ├── eslint.config.mjs
    │       ├── next-env.d.ts
    │       ├── next.config.ts
    │       ├── node_modules
    │       ├── package.json
    │       ├── postcss.config.mjs
    │       ├── public
    │       ├── src
    │       └── tsconfig.json
    ├── node_modules
    │   └── prettier -> .pnpm/prettier@3.6.2/node_modules/prettier
    ├── package.json
    ├── pnpm-lock.yaml
    └── pnpm-workspace.yaml
    
  2. 거기에 apps/cms (@strapi/strapi 5.16.1 + react ^18.0.0) 설치까지 하면 아래와 같은 에러가 터진다.

    .
    ├── apps
    │   ├── cms
    │   │   ├── package.json  // @strapi/strapi 5, react 18, etc.
    │   │   └── ...
    │   └── web
    │       ├── package.json  // next 15, react 19, etc.
    │       └── ...
    ├── package.json
    ├── pnpm-lock.yaml
    └── pnpm-workspace.yaml  // packages: apps/*
    
     ./src/app/page.tsx:7:10
     Type error: 'Fragment' cannot be used as a JSX component.
       Its type 'ExoticComponent<FragmentProps>' is not a valid JSX element type.
         Type 'ExoticComponent<FragmentProps>' is not assignable to type '(props: any, deprecatedLegacyContext?: any) => ReactNode'.
           Type 'import("/Users/ndaemy/Documents/dev/poc-pnpm-workspaces-hoisting/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index").ReactNode' is not assignable to type 'React.ReactNode'.
             Type 'ReactElement<unknown, string | JSXElementConstructor<any>>' is not assignable to type 'ReactNode'.
               Property 'children' is missing in type 'ReactElement<unknown, string | JSXElementConstructor<any>>' but required in type 'ReactPortal'.
    
        5 |     <div>
        6 |       {["a", "b", "c"].map((item) => (
     >  7 |         <Fragment key={item}>
          |          ^
        8 |           <p>{item}</p>
        9 |         </Fragment>
       10 |       ))}
    Next.js build worker exited with code: 1 and signal: null
    
    전체 folder structure
    .
    ├── apps
    │   ├── cms
    │   │   ├── README.md
    │   │   ├── config
    │   │   │   ├── admin.ts
    │   │   │   ├── api.ts
    │   │   │   ├── database.ts
    │   │   │   ├── middlewares.ts
    │   │   │   ├── plugins.ts
    │   │   │   └── server.ts
    │   │   ├── database
    │   │   │   └── migrations
    │   │   ├── favicon.png
    │   │   ├── node_modules
    │   │   │   ├── @strapi
    │   │   │   ├── @types
    │   │   │   ├── better-sqlite3 -> ../../../node_modules/.pnpm/better-sqlite3@11.3.0/node_modules/better-sqlite3
    │   │   │   ├── react -> ../../../node_modules/.pnpm/react@18.3.1/node_modules/react
    │   │   │   ├── react-dom -> ../../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom
    │   │   │   ├── react-router-dom -> ../../../node_modules/.pnpm/react-router-dom@6.30.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/react-router-dom
    │   │   │   ├── styled-components -> ../../../node_modules/.pnpm/styled-components@6.1.19_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/styled-components
    │   │   │   └── typescript -> ../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript
    │   │   ├── package.json
    │   │   ├── public
    │   │   │   ├── robots.txt
    │   │   │   └── uploads
    │   │   ├── src
    │   │   │   ├── admin
    │   │   │   ├── api
    │   │   │   ├── extensions
    │   │   │   └── index.ts
    │   │   └── tsconfig.json
    │   └── web
    │       ├── README.md
    │       ├── eslint.config.mjs
    │       ├── next-env.d.ts
    │       ├── next.config.ts
    │       ├── node_modules
    │       │   ├── @eslint
    │       │   ├── @tailwindcss
    │       │   ├── @types
    │       │   ├── eslint -> ../../../node_modules/.pnpm/eslint@9.29.0_jiti@2.4.2/node_modules/eslint
    │       │   ├── eslint-config-next -> ../../../node_modules/.pnpm/eslint-config-next@15.3.4_eslint@9.29.0_jiti@2.4.2__typescript@5.8.3/node_modules/eslint-config-next
    │       │   ├── next -> ../../../node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next
    │       │   ├── react -> ../../../node_modules/.pnpm/react@19.1.0/node_modules/react
    │       │   ├── react-dom -> ../../../node_modules/.pnpm/react-dom@19.1.0_react@19.1.0/node_modules/react-dom
    │       │   ├── tailwindcss -> ../../../node_modules/.pnpm/tailwindcss@4.1.11/node_modules/tailwindcss
    │       │   └── typescript -> ../../../node_modules/.pnpm/typescript@5.8.3/node_modules/typescript
    │       ├── package.json
    │       ├── postcss.config.mjs
    │       ├── public
    │       │   ├── file.svg
    │       │   ├── globe.svg
    │       │   ├── next.svg
    │       │   ├── vercel.svg
    │       │   └── window.svg
    │       ├── src
    │       │   └── app
    │       └── tsconfig.json
    ├── node_modules
    │   └── prettier -> .pnpm/prettier@3.6.2/node_modules/prettier
    ├── package.json
    ├── pnpm-lock.yaml
    └── pnpm-workspace.yaml
    

원인분석

  1. 터지는 에러가 React의 타입에 대한 에러이니 @types/react 에 대한 에러일 것이다.
  2. 근데 그러면 monorepo 에서 버전이 다른 두 개의 라이브러리를 설치했을 때 그것들이 사용자의 의도와 무관하게 영향을 준다는 얘기인가?

이런 가설들을 세우고 해결방법을 찾아보게 되었다.

해결방법

  1. Stack Overflow 답변

    • 요약: .npmrcshared-workspace-lockfile=false 를 추가해서 lockfile 을 hoisting 하지 않도록 막으면 해결된다.
    • 문제: 이러면 pnpm workspace 의 장점이 많이 줄어든다. node_modules 폴더의 사이즈도 비대해진다.
  2. GitHub 이슈 코멘트관련 블로그 포스트

    • 요약: 문제가 생기는 프로젝트의 tsconfig.jsoncompileOptions.paths"react": ["./node_modules/@types/react"] 를 추가하면 해결된다.
    • 아쉬움: pnpm에서 파생된 생기는 이슈를 typescript 에서 해결하는 것에 대한 찝찝함이 있다.
  3. GitHub 이슈 코멘트pnpm 공식 문서

    • 요약: .npmrc 파일에 아래와 같이 hoistPattern을 설정하여 @types/react가 최상위로 호이스팅되는 것을 막는다.
      hoistPattern:
        - "*types*"
        - "!@types/react"
      
    • 추가: 프로젝트 세팅에 따라 3번 해결책이 작동하지 않는 경우도 발생했다. 이 경우 2번 해결책을 사용하면 해결되었다.