This content originally appeared on DEV Community and was authored by Rad Code
TL;DR
When working with React Native and pnpm, just use node-linker=hoisted instead of trying to selectively hoist individual packages. It will save you hours of debugging mysterious codegen errors and Kotlin compilation issues. (If you're in a monorepo, you'll also need to update build.gradle paths to point to the root node_modules.)
The Setup
I'm working on a React Native 0.82 project in a pnpm monorepo. The structure looks like this:
do-not-stop/
├── mobile/ # React Native app
├── frontend/ # Web app
├── backend/ # API server
├── packages/ # Shared packages
└── package.json # Root workspace
The Problem
During an Android build, I encountered this error:
Error: Cannot find module 'E:\git\do-not-stop\mobile\node_modules\@react-native\codegen\lib\cli\combine\combine-js-to-schema-cli.js'
The build was failing at the generateCodegenSchemaFromJavaScript task for @react-native-async-storage/async-storage. Classic React Native codegen issues.
The Initial "Solution" (That Didn't Work)
Following the conventional wisdom, I added the missing packages to mobile/package.json:
{
"dependencies": {
"@react-native/codegen": "^0.82.1",
"@react-native/gradle-plugin": "^0.82.1"
}
}
This seemed logical - the build needs these packages, so let's add them as dependencies. Right?
Wrong.
The Rabbit Hole Deepens
After adding these dependencies, I started getting Kotlin compilation errors:
Unresolved reference 'NativeRNWalletConnectModuleSpec'
'getName' overrides nothing
'getTypedExportedConstants' overrides nothing
The errors were coming from @walletconnect/react-native-compat, which was trying to use codegen-generated classes that weren't being found.
Attempting Selective Hoisting
I tried to be clever and selectively hoist only the problematic packages. I added to mobile/package.json:
{
"pnpm": {
"hoistPattern": [
"@react-native/codegen",
"@react-native/gradle-plugin",
"@react-native/metro-config"
]
}
}
This created even more issues:
- Some packages found the codegen, others didn't
- Inconsistent paths between development and build
- Metro bundler couldn't resolve modules correctly
- Kotlin compilation still failing with different errors
The Real Solution
After hours of debugging, I finally gave up on selective hoisting and went nuclear:
Added .npmrc at the root:
node-linker=hoisted
The hoisted linker ensures all packages are in a single node_modules directory, which React Native's build system expects.
For pnpm monorepos specifically, updated mobile/android/app/build.gradle to point to the root node_modules:
react {
// Point to root node_modules since we're using hoisted linking in a monorepo
reactNativeDir = file("../../../node_modules/react-native")
codegenDir = file("../../../node_modules/@react-native/codegen")
cliFile = file("../../../node_modules/react-native/cli.js")
autolinkLibrariesWithApp()
}
The paths go up three levels because:
-
mobile/android/app/build.gradle→mobile/android/→mobile/→ root
Note: If you're using pnpm with a standalone React Native project (not a monorepo), you only need the .npmrc change. The build.gradle modifications are only necessary when your React Native app is nested in a monorepo structure.
The Lesson
Selective hoisting in React Native projects using pnpm is a false optimization. You might think: "Couldn't I just track down all the React Native-related packages that need hoisting?" Technically, yes - you could spend hours finding @react-native/codegen, @react-native/gradle-plugin, @react-native/metro-config, @react-native/babel-preset, and all the other RN packages. But React Native's dependency structure changes frequently between versions. What works today might break tomorrow when you upgrade React Native or one of its tooling packages. You'd be playing whack-a-mole with your hoist patterns every time you update.
React Native's build system expects a certain structure. When you have Gradle looking for codegen, Metro bundler resolving modules, native code trying to import generated classes, and multiple tools working together, you need consistency, not clever path resolution. Hoisting everything gives you that consistency, and it's future-proof.
That said, if you really want to save disk space and are willing to maintain a selective hoisting configuration with pnpm's hoistPattern and symlinking strategies, it's technically possible. But this requires deep knowledge of React Native's dependency graph, ongoing maintenance as packages update, and potential debugging time when things break.
If you've successfully set up selective hoisting for React Native with pnpm and want to help others, please share your configuration in the comments! Include your .npmrc settings, package.json hoist patterns, and any build.gradle modifications you made.
Conclusion
Next time you're setting up a React Native project with pnpm, just hoist everything from the start. Your builds will be reliable, and your debugging time will be spent on actual features, not dependency resolution.
This article is part of the do-not-stop project - a full-stack Web3 playground with React Native mobile support.
Have you had similar experiences with React Native and pnpm? Share your horror stories (and solutions) in the comments!
This content originally appeared on DEV Community and was authored by Rad Code
Rad Code | Sciencx (2025-11-05T05:16:50+00:00) React Native, pnpm, and Monorepos: A Dependency Hoisting Journey. Retrieved from https://www.scien.cx/2025/11/05/react-native-pnpm-and-monorepos-a-dependency-hoisting-journey/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.