Qué es un monorepo y por qué
Un monorepo no es una carpeta con muchos proyectos, sino un único repositorio con un grafo, un lockfile y una cadena de herramientas compartidos. Por qué el código compartido sin publicar, el cambio atómico entre paquetes y una sola fuente de verdad son su razón de ser.
Un monorepo no es una carpeta grande con muchos proyectos dentro: es un único repositorio cuyo contenido comparte un solo grafo de dependencias, un solo lockfile y una sola cadena de herramientas. La distinción es sutil y decisiva. Meter diez proyectos inconexos en el mismo directorio no te da nada; lo que convierte ese directorio en un monorepo es que sus piezas se conocen entre sí, se referencian sin pasar por el registro y se validan juntas en cada commit. El monorepo es, antes que una estructura de carpetas, una decisión sobre dónde vive la fuente de verdad.
- Definir un monorepo por su propiedad esencial: un grafo y una verdad compartidos.
- Distinguir monorepo de monolito y de “muchas carpetas juntas”.
- Entender el cambio atómico entre paquetes como su capacidad central.
- Situar el código compartido sin publicar como el beneficio cotidiano.
Qué es, y qué no es, un monorepo
Conviene definirlo por negación antes que por afirmación, porque casi todo el mundo llega con una intuición equivocada. Un monorepo no es tres cosas con las que se confunde:
- No es un monolito. El monolito habla de runtime —una unidad que se despliega y ejecuta junta—; el monorepo habla de topología del código fuente —dónde se guarda y cómo se versiona—. Un monorepo puede contener veinte servicios que se despliegan por separado y seguir siendo un monorepo.
- No es un cajón de sastre. Amontonar proyectos sin relación bajo una historia de git común no es un monorepo: es un directorio con inquilinos que se ignoran.
- No es cuestión de cuántas carpetas hay. La topología no la define el número de directorios, sino la existencia de un grafo y una historia comunes que permiten el cambio transversal.
Lo que sí es: un repositorio con coherencia interna. Sus paquetes se importan unos a otros con el protocolo workspace:, no con una versión publicada; comparten configuración de tipos y de lint; y una sola instalación reconstruye todo el árbol desde cero. La idea no es nueva: Google versiona miles de millones de líneas en un único repositorio con Piper, y Meta hizo lo propio hasta tener que reescribir el control de versiones —Sapling, EdenFS— porque git no aguantaba la escala. Lo nuevo en 2026 es que el monorepo se ha democratizado: pnpm resuelve y enlaza el grafo, y un orquestador como Turborepo o Nx aporta la caché, de modo que un equipo de tres personas disfruta del mismo modelo que antes exigía la infraestructura de una big tech.
flowchart TD LOCK[un solo pnpm-lock.yaml] --> GRAFO[un unico grafo resuelto] GRAFO --> UI[packages ui] GRAFO --> WEB[apps web] GRAFO --> ADMIN[apps admin] UI --> WEB UI --> ADMIN style LOCK fill:#cba6f7,color:#11111b style GRAFO fill:#89b4fa,color:#11111b
Para decidir si algo es un monorepo, hazte una sola pregunta: ¿podría un único commit cambiar dos de sus paquetes a la vez y validarse junto? Si la respuesta es sí, hay monorepo; si cada paquete solo puede cambiarse y publicarse por separado, es un polyrepo con carpetas vecinas. El criterio no es la cantidad de carpetas, sino la posibilidad del cambio atómico.
Código compartido: el beneficio diario
De los tres pilares, el código compartido es el que se nota a diario. En un polyrepo, para reutilizar un componente lo publicas como paquete, esperas a que aparezca en el registro, lo instalas y fijas su versión. En un monorepo escribes una línea y desaparece todo ese ceremonial.
// packages/ui/package.json
{ "name": "@acme/ui", "version": "0.0.0", "private": true }
// apps/web/package.json
{ "dependencies": { "@acme/ui": "workspace:*" } }
El protocolo workspace:* le dice a pnpm “resuelve esto contra el paquete que vive en este mismo repo, no contra el registro”. No hay ciclo de publicación, no hay latencia, no hay versión que sincronizar. Editas @acme/ui y apps/web ve el cambio en el acto, porque no consume una copia publicada sino el paquete real enlazado por symlink.
Esa inmediatez tiene un corolario que se agradece a diario: para el código interno no existe deriva de versiones. No hay número que sincronizar entre @acme/ui y apps/web, porque no media una versión publicada sino un enlace al paquete real. La versión reaparece solo en la frontera del repo, cuando —y si— publicas ese paquete al mundo exterior.
# el enlace es literal, no una copia del registro
pnpm why @acme/ui
# apps/web depende de @acme/ui link:../../packages/ui
Si un paquete interno acaba publicándose, no te preocupes por el workspace:*: al empaquetar, pnpm y Changesets lo reescriben a la versión concreta que corresponde. Dentro del repo consumes el enlace vivo; fuera, el mundo ve un rango de versión normal. Es la misma dualidad que permite que el código compartido interno no tenga versión, pero el publicado sí.
Código compartido
Una librería interna se consume con workspace:*: sin publicar, sin versionar, sin esperar al registro. El consumidor ve el paquete como si fuera local, porque lo es.
Cambio atómico
Un solo commit puede cruzar fronteras de paquete: cambias una API y actualizas a todos sus consumidores a la vez, y el CI valida el conjunto.
Fuente de verdad única
Un lockfile, una política de versiones, una configuración de tooling. El repositorio entero es, en todo momento, internamente consistente.
Reproducibilidad
Clonar, instalar y construir produce el mismo resultado para cualquiera. El grafo completo se materializa desde un solo origen.
El cambio atómico: la capacidad central
Si tuvieras que quedarte con una sola razón para adoptar un monorepo, sería esta. En un polyrepo, renombrar una función exportada por una librería es una coreografía: cambias la librería, publicas una versión nueva, esperas, y luego abres un pull request por cada repositorio consumidor para subir la versión y adaptar las llamadas. Durante ese intervalo el ecosistema está desincronizado: unos consumidores ya usan la versión nueva y otros no. Es el clásico problema de la dependencia en diamante, donde dos ramas del grafo exigen versiones incompatibles de lo mismo.
En un monorepo, ese mismo cambio es un único commit. Renombras la función en packages/utils y, en la misma revisión, actualizas a sus diez consumidores; el CI construye y prueba el conjunto de una sola vez.
# testea utils y, en cascada, todo lo que depende de el
pnpm --filter "...@acme/utils" run test
flowchart TD CH[cambias una API en packages utils] --> ONE[mismo commit actualiza 10 consumidores] ONE --> CI[un solo PR el CI valida el conjunto] POLY[en polyrepo publicar esperar y hacer bump por repo] --> DRIFT[ecosistema desincronizado entre pasos] style CI fill:#a6e3a1,color:#11111b style DRIFT fill:#eba0ac,color:#11111b
La consecuencia profunda es que el monorepo elimina una clase entera de trabajo: la sincronización de versiones entre repos. No hay “primero mergeo esto, publico, y mañana actualizo lo otro”. Hay un solo estado, el HEAD del repositorio, y siempre es coherente consigo mismo. Los grandes refactors —renombrar, cambiar una firma, migrar una API— dejan de ser proyectos de semanas repartidos en veinte PRs y pasan a ser un commit revisable de una sentada.
Este es también el terreno donde los codemods rinden al máximo. Una transformación automatizada —con jscodeshift, con el refactor del editor o con un agente— reescribe un patrón en los cuarenta paquetes de una sola pasada, porque todos están en el árbol de trabajo a la vez. En un polyrepo, ese mismo codemod hay que clonarlo, aplicarlo y abrir un PR repo por repo, perdiendo justo la atomicidad que lo hacía fiable.
La palabra no es decorativa: el cambio entra entero o no entra. Si el refactor rompe a un consumidor, el CI falla sobre el commit completo y nada se mergea, de modo que main nunca queda en el estado intermedio donde la librería ya cambió pero un consumidor aún no. Esa indivisibilidad es precisamente lo que el polyrepo no puede dar: entre publicar la librería y actualizar al último consumidor siempre existe una ventana en la que el sistema, en conjunto, es incoherente.
Una sola fuente de verdad
El tercer pilar es el más silencioso y el que sostiene a los otros dos. Un solo pnpm-lock.yaml en la raíz fija una única resolución del grafo para todo el repositorio: cualquiera que clone e instale obtiene exactamente el mismo árbol de dependencias. La configuración de TypeScript, de lint y de formato vive en paquetes de tooling/ que todos extienden, de modo que “cómo se comprueban los tipos aquí” tiene una sola respuesta y no cuarenta copias divergentes.
Esa unicidad es lo que hace posible el cambio atómico. Puedes actualizar a diez consumidores en un commit precisamente porque los diez comparten el mismo grafo resuelto y la misma configuración; no estás integrando diez mundos distintos, estás editando uno solo. La fuente de verdad única no es un lujo organizativo: es la condición técnica que permite todo lo demás.
# cualquiera reproduce el repo entero desde un solo origen
git clone "$REPO" && cd repo
pnpm install --frozen-lockfile # instala EXACTAMENTE el grafo del lockfile
pnpm -r build # construye todo desde una sola verdad
No es un detalle menor: significa que el estado del repositorio es una función determinista de su HEAD. Dos personas, dos máquinas, el CI y tu portátil derivan todos el mismo árbol de dependencias del mismo lockfile. La reproducibilidad deja de ser una aspiración y pasa a ser una propiedad que se sigue, sin más, de construir sobre una única fuente.
Y esa propiedad es la que encadena los tres pilares en uno solo: hay código compartido sin fricción porque hay un grafo único; hay cambio atómico porque ese grafo se resuelve una sola vez; y hay una sola verdad porque un único lockfile la fija. No son tres beneficios sueltos, son tres caras de la misma decisión. Por eso conviene no vender el monorepo como “una carpeta con todo junto”: lo que se compra al adoptarlo es, exactamente, esa unicidad —un grafo, un lockfile, una configuración—, y todo lo demás se deriva de ahí.
El pnpm-lock.yaml de la raíz es innegociable: uno solo, commiteado siempre, e instalado en CI con --frozen-lockfile para que el pipeline falle si no cuadra con los package.json. Es lo que hace literal la frase “una sola fuente de verdad”: no una aspiración cultural, sino un archivo del que se deriva, de forma determinista, el grafo entero que verá cualquiera que clone el repo.
El cambio atómico te permite cruzar fronteras de paquete, pero no te obliga a respetarlas. Sin disciplina —reglas de dependencia, fronteras impuestas por tooling— esa misma facilidad degenera en acoplamiento: todo importa de todo porque puede. El monorepo te da la potencia; las convenciones y el orquestador son los que evitan que la conviertas en una bola de barro. Lo veremos en las lecciones de estructura y de retos.
Toda arquitectura de repositorios responde, en el fondo, a una sola pregunta: cuándo pagas el coste de integrar tus piezas. El polyrepo lo difiere. Cada librería vive aislada, se publica con su versión, y la integración —comprobar que todo encaja— ocurre tarde, cuando alguien instala la combinación concreta de versiones y descubre que dos de ellas ya no casan. Esa deuda de integración se acumula en silencio y estalla lejos de donde se originó. El monorepo hace lo contrario: integra continuamente. Cada commit se valida contra el estado actual de todo lo demás, porque todo lo demás está ahí, a un import de distancia, en la misma revisión. La fuente de verdad no es un conjunto de versiones publicadas que hay que reconciliar, sino un único HEAD que, por construcción, siempre es coherente. Por eso el monorepo brilla en el cambio atómico: renombrar una función y actualizar a sus consumidores no es coordinar N repositorios, es editar un archivo y guardar. Y por eso su promesa más honda no es la velocidad —cachear, no recompilar, eso vendrá con las herramientas— sino la coherencia: la garantía de que en ningún instante el repositorio se contradice a sí mismo. Interiorizar esto cambia cómo lees la decisión. No estás eligiendo entre “una carpeta o muchas”; estás eligiendo entre pagar la integración a plazos, con intereses y sorpresas, o pagarla al contado en cada commit. Todo lo que sigue —la estructura, los retos, las herramientas— son consecuencias de haber elegido pagar al contado.
- Abre un proyecto tuyo con más de un paquete —o dos repos que se consuman— y describe cómo compartís código hoy: por registro, por copia, o por
workspace:. - Localiza el último cambio que tuviste que replicar en varios repos o carpetas. ¿Cuántos pasos y cuánta espera costó? En un monorepo, ¿habría sido un commit?
- Escribe un
pnpm-workspace.yamlmínimo conapps/*ypackages/*y declara una dependencia interna conworkspace:*. - Ejecuta
pnpm --filter "...<paquete>" run buildy observa cómo pnpm arrastra al paquete y a todos sus dependientes en orden. - En una frase, responde: ¿cuál es hoy la fuente de verdad de las versiones y la configuración en tu código? Si no hay una, ya sabes qué promete el monorepo.