Monorepos con pnpm: cuando dejan de doler y empiezan a servir
Por Equipo Hexadevs · 28 ago 2026 · 3 min de lectura
Un monorepo mal configurado es una de las fuentes más comunes de fricción en equipos chicos y medianos. Un monorepo bien configurado, en cambio, te devuelve atomicidad en los cambios, código compartido sin publicar paquetes y CI que corre solo lo necesario.
Por qué pnpm y no npm workspaces
npm puede hacer workspaces desde la versión 7, pero pnpm resuelve tres problemas que npm y yarn no:
- No duplica dependencias en disco. Un proyecto con 15 paquetes no instala 15 copias de React; usa symlinks a un store global. Según los benchmarks del equipo de pnpm, instalación hasta 3x más rápida, y un árbol de
node_modulesplano. --filterpara ejecutar comandos selectivamente. Correr tests solo en el paquete afectado y sus dependientes.- Strict mode por defecto, que evita que un paquete acceda a dependencias que no declaró explícitamente. Esto previene el clásico bug de “funciona en este paquete pero revienta cuando lo importás desde otro”.
Estructura mínima viable
El típico monorepo con pnpm y workspaces:
hexadevs/package.json— workspace roothexadevs/pnpm-workspace.yamlhexadevs/packages/ui/— componentes compartidoshexadevs/packages/eslint-config/— configs compartidashexadevs/packages/tsconfig/— tsconfigs basehexadevs/apps/web/— front (en nuestro caso Astro)hexadevs/apps/api/— backend (Hono server)
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
// package.json (root)
{
"name": "hexadevs",
"private": true,
"scripts": {
"dev": "pnpm --filter \"./apps/*\" run dev",
"build": "pnpm -r run build",
"test": "pnpm -r --parallel run test",
"lint": "pnpm -r run lint"
}
}
Tasks recursivas y filtrado, las dos joyitas
# Un paquete y sus dependencias
pnpm --filter @hexadevs/ui... run build
# Solo los paquetes que dependen de ui (sin incluir ui)
pnpm --filter ...^@hexadevs/ui run test
# Por nombre con glob
pnpm --filter "./apps/*" run dev
Con estas tres formas de filtrado resolvés casi todos los casos. El resto se cubre con --filter-prod, --parallel y --topological.
Dependencias entre paquetes: el detalle que rompe todo
Acá está el gotcha más común. Para que apps/web use packages/ui correctamente:
// apps/web/package.json
{
"dependencies": {
"@hexadevs/ui": "workspace:*"
}
}
workspace:* es la sintaxis de pnpm: apunta a la versión local del paquete y, al publicar, pnpm la reemplaza por la versión real. Nunca uses ^1.0.0 ni link: para esto: rompe la actualización de versiones entre paquetes.
La capa que falta: caché de tareas con Turborepo
pnpm ordena y filtra, pero sigue siendo serial y ciego: pnpm -r run build vuelve a construir los 6 paquetes aunque solo cambió uno. Esa capa la resuelve Turborepo, que corre tus scripts entendiendo el grafo de dependencias, en paralelo y con caché por contenido:
// turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
pnpm exec turbo run build
dependsOn: ["^build"] construye primero las dependencias, y outputs le dice a Turborepo qué guardar en su caché. Si el hash del código fuente y de sus dependencias no cambió, el build se restaura de ahí (en la terminal lo vas a ver como cache hit, replaying logs). En CI el salto es aún mayor con el remote cache: todos los runners comparten la caché, y el build que tu compañero ya hizo no vuelve a computarse.
En nuestro monorepo conviven las dos herramientas sin pelearse: pnpm maneja dependencias (instalar, filtrar, vincular workspaces) y Turborepo maneja tareas (build, check, test) en local y en CI.
Si tu equipo tiene más de 3 desarrolladores y más de un deploy, un monorepo con pnpm deja de ser opcional. Es la diferencia entre coordinar releases y hacer un commit atómico que toca todos los paquetes afectados a la vez.
Cuando empieces, mantené el monorepo chiquito: 3 a 5 paquetes. Sumá uno nuevo solo cuando hay un caso claro de código compartido. Los monorepos grandes sin justificación son la forma más rápida de arruinar la productividad de un equipo.