Corré du -sh node_modules en dos proyectos: uno chico, con quince dependencias, y un monorepo con cuatro apps que comparten paquetes internos. En el primero, no vas a notar diferencia entre npm y pnpm. En el segundo, si seguís con npm, vas a tener la misma copia de React, TypeScript y las mismas devDependencies repetidas cuatro veces en disco — una por cada paquete del workspace que las declara.
Ese es el problema real. No es "pnpm es más rápido" en abstracto. Es que el ahorro de espacio y tiempo depende de cuánta duplicación tenía tu instalación original, y esa duplicación crece con la cantidad de paquetes del monorepo, no con la cantidad de dependencias totales.
Mi tesis: pnpm gana en monorepos y en CI por el modelo de symlinks, pero npm sigue siendo la opción sin fricción para proyectos chicos donde la curva de aprendizaje no vale la pena. No es una preferencia estética. Es una cuenta que cambia según el tamaño del repo.
Diferencia entre npm y pnpm: qué guarda cada uno en disco
npm instala cada dependencia como una copia física dentro de node_modules. Si tenés cuatro paquetes en un workspace y los cuatro dependen de [email protected], npm — salvo con hoisting agresivo que trae sus propios problemas de fantom dependencies — puede terminar con esa misma versión copiada en más de un lugar del árbol.
pnpm hace algo distinto desde el diseño. Según la documentación oficial de motivación de pnpm, el paquete se descarga una sola vez y se guarda en un content-addressable store global, ubicado fuera del proyecto. Cada node_modules de cada paquete del monorepo no tiene una copia: tiene un symlink que apunta a esa store compartida.
La consecuencia práctica: si tenés diez proyectos en tu máquina que usan la misma versión de una librería, esa versión existe una sola vez en disco. Los diez node_modules apuntan al mismo lugar.
# Ver dónde vive la store global de pnpm
pnpm store path
# Ver cuánto ahorra la store comparado con una instalación no deduplicada
pnpm store statusEsto no es magia de pnpm inventada de la nada — es el mismo principio que usan los gestores de paquetes de sistemas operativos con hardlinks, aplicado a Node. La documentación de pnpm es clara en para qué está pensado el modelo: evitar la duplicación de bytes idénticos en disco cuando hay múltiples proyectos o múltiples paquetes dentro de un mismo repo.
Lo que esa fuente no dice es que esto te va a ahorrar tiempo de build en cualquier escenario. Ahorra espacio en disco de forma consistente. El ahorro de tiempo de instalación depende de si el paquete ya está en tu store local — en una corrida de CI con caché fría, la primera descarga sigue pesando igual.
Dónde se equivoca la gente: migrar por hype, no por necesidad
La receta que veo repetida: proyecto Next.js chico, alguien lee un thread sobre pnpm, migra el lockfile, borra node_modules, corre pnpm install, y listo — "ahora somos más rápidos". El costo oculto aparece después, no en el proyecto en sí sino en el resto del stack que asume npm: algunos scripts de CI, algunas imágenes Docker con RUN npm ci, algún linter de dependencias que parsea package-lock.json con el formato descripto en la documentación oficial de npm y no entiende pnpm-lock.yaml.
El contraejemplo: un proyecto con un solo package.json, sin workspaces, con diez dependencias de producción. Ahí la diferencia entre npm y pnpm en tiempo de instalación es marginal y el modelo de symlinks no tiene nada para deduplicar — no hay otro paquete del mismo repo pidiendo la misma librería. Migrar ese proyecto a pnpm no trae ahorro medible; trae una herramienta nueva que el equipo tiene que aprender a diagnosticar cuando algo falla.
Esto conecta con algo que ya charlé sobre orquestación con Docker: la fricción de una migración de tooling no está en el comando de instalación. Está en todos los lugares donde alguien asumió el comportamiento anterior sin documentarlo.
Matriz de decisión: cuándo pnpm pesa y cuándo es ruido
| Escenario | ¿pnpm ahorra algo real? | Qué mirar primero |
|---|---|---|
| Monorepo con 3+ paquetes que comparten dependencias | Sí — menos bytes duplicados en disco | Cuántas dependencias son compartidas entre paquetes |
| CI con caché de dependencias entre corridas | Sí, si el runner soporta cachear la store global | Configuración de caché del pipeline, no solo el lockfile |
| Proyecto único, sin workspaces | Marginal | Si el equipo ya conoce pnpm o hay que entrenarlo |
| Imágenes Docker con build multistage | Depende — hay que ajustar el Dockerfile para copiar la store | Si el ahorro de espacio en imagen justifica el cambio de recipe |
Equipo con scripts npm-específicos (npm run, npx) en CI/CD ya maduro | No, salvo que planeen reescribir esos scripts | Costo de reescritura vs beneficio esperado |
Esta matriz no reemplaza medir en tu propio proyecto. Cada fila es un criterio para empezar a mirar, no una conclusión cerrada.
Los límites de esta comparación
No tengo un benchmark propio con números de tiempo de instalación para publicar acá, y no lo voy a inventar. Lo que se puede afirmar con la fuente disponible es el modelo — content-addressable store con symlinks — y de ahí se deriva lógicamente que la deduplicación escala con la cantidad de paquetes en el workspace. Lo que no se puede concluir sin correr un experimento reproducible en un proyecto concreto:
- Cuánto tiempo exacto ahorra pnpm en una corrida de CI: depende del runner, del tamaño de la caché, de la red.
- Si el ahorro de espacio en disco justifica el costo de reescribir configuración de Docker o de linters que dependen del formato del lockfile.
- Cómo se comporta con dependencias nativas que compilan binarios distintos por symlink — ese es un caso donde el modelo de pnpm puede generar fricción adicional que no cubre este post.
Si necesitás ese dato para una decisión de equipo, la única forma honesta de tenerlo es correr pnpm install y npm install en el mismo proyecto, con la misma caché fría, y medir vos mismo. Nadie más te puede dar ese número con validez para tu caso específico.
Monorepos, workspaces y por qué ahí el modelo importa más
En un monorepo con TypeScript y Next.js 16, donde varias apps comparten un paquete de UI interno o un set de tipos, la pregunta no es "qué instala más rápido" sino "qué tan bien maneja las referencias cruzadas entre paquetes del workspace". pnpm resuelve esto con workspace:* en el package.json de cada paquete, y el symlink apunta directo al paquete hermano en el monorepo — sin pasar por el registro.
flowchart LR
A[paquete-ui] -->|symlink workspace| B[store pnpm global]
C[app-web] -->|symlink workspace| A
D[app-admin] -->|symlink workspace| A
B -->|una sola copia| E[(react, typescript, etc)]npm también soporta workspaces desde la v7, con un modelo de hoisting hacia la raíz del monorepo. Funciona, pero no deduplica entre monorepos distintos en la misma máquina — cada repo vuelve a descargar y a guardar su propia copia física. Ahí está la diferencia de fondo entre npm y pnpm: no es una feature que uno tiene y el otro no, es una decisión de arquitectura sobre dónde vive el byte.
Si tu proyecto no tiene ese problema — porque es un solo paquete, sin submódulos, sin apps hermanas — el modelo de pnpm no tiene nada que optimizar. La curva de aprendizaje del equipo, entender .pnpmfile.cjs, entender por qué algunas dependencias fantasma que antes "colaban" con npm ahora fallan con ERR_PNPM_NO_MATCHING_VERSION porque pnpm es más estricto con las dependencias no declaradas — todo eso es costo real que hay que poner en la balanza.
FAQ
¿pnpm es compatible con los scripts de npm existentes?
Sí, en general pnpm run ejecuta los mismos scripts definidos en package.json. Los problemas aparecen en scripts que asumen dependencias fantasma — paquetes que no están declarados pero que npm dejaba accesibles por el hoisting. pnpm es más estricto y esos scripts pueden romper.
¿Puedo tener package-lock.json y pnpm-lock.yaml al mismo tiempo? Técnicamente los archivos pueden coexistir, pero mezclar gestores de paquetes en el mismo proyecto genera instalaciones inconsistentes entre miembros del equipo. Elegí uno y borrá el lockfile del otro.
¿pnpm sirve para un proyecto Next.js chico sin monorepo? Funciona sin problema, pero el ahorro de espacio y tiempo que motiva el cambio depende de deduplicación entre paquetes — algo que un proyecto único no tiene para aprovechar. Ahí la decisión se reduce a preferencia del equipo, no a una ganancia medible.
¿El content-addressable store de pnpm rompe algo con dependencias nativas?
Puede generar fricción con paquetes que compilan binarios nativos (node-gyp) porque el symlink apunta a una ubicación compartida. No es un bloqueo generalizado, pero es un caso que conviene probar antes de migrar un proyecto que dependa de esas librerías.
¿Cómo migro un proyecto de npm a pnpm sin romper el CI?
Corré pnpm import para generar el pnpm-lock.yaml desde el package-lock.json existente, actualizá los pasos de CI que invocan npm ci por pnpm install --frozen-lockfile, y revisá cualquier Dockerfile que asuma la estructura plana de node_modules de npm.
¿Yarn entra en esta comparación? Yarn Berry (v2+) también usa un modelo distinto al npm clásico, con Plug'n'Play opcional. No lo cubro en detalle acá porque el eje de este post es específicamente el modelo de store de pnpm versus el árbol de npm, pero la misma pregunta — ¿cuánta duplicación tiene tu proyecto real? — aplica igual.
Postura final
Si tu proyecto es un monorepo con paquetes que se pisan en dependencias, o si tu pipeline de CI corre instalaciones repetidas todos los días, el modelo de symlinks de pnpm no es hype — es una decisión de arquitectura que reduce bytes duplicados de forma verificable, según describe la propia documentación de pnpm. Si tu proyecto es chico y solo, la curva de aprendizaje de pnpm — sus reglas más estrictas, sus mensajes de error distintos, la necesidad de que todo el equipo lo tenga instalado — no se paga con nada.
Mi recomendación práctica: no migres porque lo vieron en un thread. Contá cuántos paquetes de tu workspace comparten dependencias. Si ese número es cero o uno, quedate en npm. Si son tres o más, corré el experimento — pnpm import, medí en tu propio pipeline, y decidí con ese dato, no con el mío.
Fuente original:
- pnpm docs - Motivation: https://pnpm.io/motivation
- npm docs - package-lock.json: https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json
Artículos Relacionados
tsconfig paths en Next.js 16 App Router: cuándo ayudan y cuándo rompen el build sin aviso
Los path aliases parecen inocentes hasta que el build de producción falla sin mensaje claro. Documenté los 3 casos de rotura más comunes en un monorepo con Next.js 16 App Router y TypeScript estricto, y el patrón de configuración que sobrevivió.
14 ago 2026 · 9′ · Tutoriales · TypeScript · pnpm
Functional programming con TypeScript: lo que fp-ts enseña aunque no lo uses en producción
fp-ts es una universidad, no un framework de producción para la mayoría. Pero ignorarlo completamente es dejar conceptos valiosos sobre la mesa. Recorrido honesto por Option, Either y pipe desde TypeScript estricto del día a día, con la postura de por qué la librería no es el punto.
07 ago 2026 · 10′ · Tutoriales · Next.js · TypeScript
Strict null checks en TypeScript: lo que el compilador no te dice y dónde sí duele en producción
El compilador dice OK. Runtime explota igual. Acá están los 4 patrones donde strict null checks no alcanza: assertion functions, librerías sin tipos, Prisma ORM y JSON.parse — con código real del stack Next.js/Prisma.
23 jul 2026 · 8′ · Tutoriales · Next.js · TypeScript
Comentarios (0)
¿Qué pensás de esto?
Dejá tu comentario en 10 segundos.
Usamos tu login solo para mostrar tu nombre y avatar. Nada de spam.
Todavía nadie comentó. Sé el primero — tu opinión vale oro cuando somos pocos.