Do-Not-Stop is a continuously evolving Web3 frontend playground — built with Vite, React, TypeScript, viem, and wagmi.
It’s designed to grow, adapt, and experiment with the latest in Ethereum, Solana, and React Native development — a living project that explores how a single modern codebase can span web and mobile in the Web3 space.
Naturally, I used pnpm workspaces. They’re fast, clean, and perfect for sharing code between multiple projects.
Until Metro — React Native’s bundler — decided it hated my setup.
🏗 The Setup
do-not-stop/
├─ frontend/ ← web (Vite + React + wagmi)
├─ mobile/ ← React Native
├─ packages/shared-auth/ ← shared logic between web & mobile
└─ pnpm-workspace.yaml
The goal was simple: import packages/shared-auth into both web and mobile.
Everything looked good — pnpm install was blazing fast, dependencies deduped — until Metro threw this:
Error: Unable to resolve module @babel/runtime/helpers/interopRequireDefault
Even though mobile/node_modules/@babel/runtime clearly existed.
🧩 Where Everything Fell Apart
Here’s the thing about pnpm: it doesn’t install packages “flat” like npm or yarn.
Instead, it builds a virtual store under .pnpm/ and links everything through symlinks.
Example:
mobile/node_modules/@babel/runtime
→ ../../node_modules/.pnpm/@babel+runtime@7.x/node_modules/@babel/runtime
Node can follow that fine, but Metro’s resolver often doesn’t.
It just climbs directories until it finds a node_modules, so it ends up loading stuff from the workspace root, not from the mobile app’s local copy.
🧠 I Tried Everything First
When you start googling “pnpm react native @babel/runtime,” you end up in GitHub issues and Stack Overflow threads full of half-solutions.
I tried disabling hoisting:
hoistPattern: []
Then isolating linkers:
node-linker=isolated
Then the nuclear option:
shamefully-hoist=true
Each time, something else broke — either the mobile build, or the shared packages stopped linking, or Metro just refused to cooperate.
Nothing really fixed it.
⚙️ The Real Fix — Teach Metro to Understand pnpm
The problem wasn’t pnpm.
The problem was Metro’s assumptions.
It expects a flat node_modules world, but pnpm builds a smart, symlinked tree.
So instead of forcing pnpm to behave like npm, I made Metro understand pnpm.
Here’s the final working metro.config.js inside my mobile/ folder:
const path = require('path');
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
/**
* Metro configuration for pnpm monorepos
* (do-not-stop project)
*
* - Lets Metro follow pnpm's symlinks
* - Always prefers the mobile app's own node_modules
* - Watches the monorepo root so shared packages rebuild correctly
*/
const defaultConfig = getDefaultConfig(__dirname);
const config = {
watchFolders: [path.resolve(__dirname, '..')],
resolver: {
...defaultConfig.resolver,
unstable_enableSymlinks: true,
unstable_enablePackageExports: true,
extraNodeModules: new Proxy(
{},
{
get: (_target, name) =>
path.join(__dirname, 'node_modules', String(name)),
}
),
},
};
module.exports = mergeConfig(defaultConfig, config);
🧪 Bonus: Using Shared Packages
Because watchFolders includes the repo root, Metro can detect changes in packages/shared-auth/ (or any shared package).
If you want finer control:
watchFolders: [
path.resolve(__dirname, '..', 'packages', 'shared-auth'),
path.resolve(__dirname, '..', 'packages', 'utils'),
],
🧠 Takeaways
- pnpm’s layout isn’t broken — it’s just smarter than Metro expects.
- Disabling hoisting or flattening installs only hides the issue.
- The real solution lives in Metro’s resolver config, not your package manager.
- When mixing web + mobile in a single repo, configure your tools to coexist — not compete.
🎉 Conclusion
This setup now powers do-not-stop:
- A React web app (
frontend/) - A React Native app (
mobile/) - Shared code in
packages/shared-auth/ - All managed by pnpm
Do-Not-Stop isn’t just a playground anymore — it’s an evolving multi-platform Web3 stack that grows alongside the latest in Ethereum, Solana, and modern React development.
After a week of battling hoisting, symlinks, and Babel errors, I stopped fighting pnpm and just made Metro smarter.
If you’re running React Native inside a pnpm monorepo, drop this config in your app and get back to building 🚀
Top comments (0)