Monorepos en JavaScript: Guía Completa con Turborepo y Nx en 2025
Hola HaWkers, monorepos se consolidaron como arquitectura preferida para proyectos JavaScript de escala. En 2025, con herramientas como Turborepo y Nx maduras, la adopción explotó en startups y grandes empresas.
¿Ya te encontraste gerenciando múltiples repositorios con código duplicado y dependencias desincronizadas? Monorepos resuelven exactamente estos problemas.
Por Qué Monorepos en 2025
Problemas de Polyrepos
Escenario típico:
repos/
├── frontend-web/
├── frontend-mobile/
├── backend-api/
├── shared-utils/
├── design-system/
└── ... 10+ más repositoriosDolores:
- Duplicación de código y configuraciones
- Sincronizar versiones de dependencias entre repos
- PRs que afectan múltiples repos
- Setup de ambiente para nuevos desarrolladores
- CI/CD fragmentado
Beneficios del Monorepo
monorepo/
├── apps/
│ ├── web/
│ ├── mobile/
│ └── api/
├── packages/
│ ├── ui/
│ ├── utils/
│ └── config/
└── package.jsonGanancias:
- Código compartido sin publicar en npm
- Refactorings atómicos (un PR cambia todo)
- Configuraciones centralizadas
- Dependencias unificadas
- CI/CD optimizado con caching
Turborepo: Guía Completa
Turborepo ganó tracción por su simplicidad y velocidad. Es la elección más popular para nuevos monorepos.
Setup Inicial
# Crear nuevo monorepo con Turborepo
npx create-turbo@latest
# O añadir a proyecto existente
npm install turbo --save-devEstructura de Proyecto
mi-monorepo/
├── apps/
│ ├── web/ # Next.js app
│ │ ├── package.json
│ │ └── ...
│ ├── docs/ # Docusaurus
│ │ ├── package.json
│ │ └── ...
│ └── api/ # Express/Fastify
│ ├── package.json
│ └── ...
├── packages/
│ ├── ui/ # Componentes compartidos
│ │ ├── package.json
│ │ └── ...
│ ├── utils/ # Funciones utilitarias
│ │ ├── package.json
│ │ └── ...
│ ├── config/ # ESLint, TypeScript configs
│ │ ├── eslint/
│ │ └── typescript/
│ └── database/ # Prisma schema y cliente
│ ├── package.json
│ └── ...
├── turbo.json
├── package.json
└── pnpm-workspace.yamlConfiguración del Turborepo
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"dev": {
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["build", "test", "lint"],
"outputs": []
}
}
}// package.json (raíz)
{
"name": "mi-monorepo",
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"test": "turbo run test",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"devDependencies": {
"turbo": "^2.0.0",
"prettier": "^3.0.0"
},
"packageManager": "pnpm@8.15.0"
}# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
Configurando Packages Compartidos
// packages/ui/package.json
{
"name": "@monorepo/ui",
"version": "0.0.0",
"private": true,
"exports": {
"./button": "./src/button.tsx",
"./card": "./src/card.tsx",
"./input": "./src/input.tsx"
},
"devDependencies": {
"@monorepo/config": "workspace:*",
"react": "^18.2.0",
"typescript": "^5.3.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
}// packages/ui/src/button.tsx
import { forwardRef } from 'react';
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', className, children, ...props }, ref) => {
const baseStyles = 'rounded-lg font-medium transition-colors';
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
ghost: 'bg-transparent hover:bg-gray-100',
};
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
return (
<button
ref={ref}
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
{...props}
>
{children}
</button>
);
}
);
Button.displayName = 'Button';Usando Package en App
// apps/web/package.json
{
"name": "web",
"version": "0.0.0",
"private": true,
"dependencies": {
"@monorepo/ui": "workspace:*",
"@monorepo/utils": "workspace:*",
"next": "^14.0.0",
"react": "^18.2.0"
}
}// apps/web/app/page.tsx
import { Button } from '@monorepo/ui/button';
import { formatDate } from '@monorepo/utils';
export default function HomePage() {
return (
<main>
<h1>Mi App</h1>
<p>Fecha: {formatDate(new Date())}</p>
<Button variant="primary" onClick={() => alert('Clicked!')}>
Haz clic aquí
</Button>
</main>
);
}
Nx: Guía Completa
Nx es más robusto y ofrece más features out-of-box. Ideal para equipos grandes y monorepos complejos.
Setup Inicial
# Crear nuevo monorepo con Nx
npx create-nx-workspace@latest
# Opciones:
# - integrated: Nx gerencia todo
# - package-based: Similar a Turborepo
# - standalone: Single app con Nx
# Añadir a proyecto existente
npx nx@latest initEstructura de Proyecto Nx
nx-monorepo/
├── apps/
│ ├── web/
│ ├── web-e2e/ # E2E tests automáticos
│ └── api/
├── libs/
│ ├── shared/
│ │ ├── ui/
│ │ └── utils/
│ └── feature/
│ ├── auth/
│ └── dashboard/
├── tools/
│ └── generators/ # Generadores customizados
├── nx.json
├── package.json
└── tsconfig.base.jsonConfiguración del Nx
// nx.json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json"
],
"sharedGlobals": []
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production"],
"cache": true
},
"lint": {
"inputs": ["default"],
"cache": true
}
},
"defaultBase": "main"
}Generators del Nx
Nx tiene generators poderosos para crear código:
# Crear nuevo app React
nx g @nx/react:app my-app
# Crear biblioteca compartida
nx g @nx/react:lib ui --directory=shared
# Crear componente en biblioteca
nx g @nx/react:component button --project=shared-ui
# Mover proyecto
nx g @nx/workspace:move --project=old-lib --destination=new-path
# Remover proyecto
nx g @nx/workspace:remove old-libAffected Commands
Una de las features más poderosas del Nx:
# Solo build proyectos afectados por cambios
nx affected:build
# Solo test proyectos afectados
nx affected:test
# Solo lint proyectos afectados
nx affected:lint
# Ver grafo de dependencias
nx graph
# Ver qué proyectos son afectados
nx show affected
Comparativo: Turborepo vs Nx
Tabla Comparativa
| Feature | Turborepo | Nx |
|---|---|---|
| Setup inicial | Más simple | Más complejo |
| Curva de aprendizaje | Baja | Media-alta |
| Generators | No tiene | Muy poderosos |
| Affected commands | Básico | Avanzado |
| Remote caching | Sí (Vercel) | Sí (Nx Cloud) |
| Grafo de dependencias | Básico | Avanzado |
| Plugins/Extensibilidad | Limitado | Muy extensible |
| Performance | Excelente | Excelente |
| Documentación | Buena | Excelente |
| Comunidad | Creciente | Grande |
Cuándo Usar Cada Uno
Elija Turborepo si:
- Equipo pequeño/mediano
- Quiere setup rápido
- Prefiere simplicidad sobre features
- Ya usa Vercel (caching gratuito)
- Monorepo con pocos proyectos
Elija Nx si:
- Equipo grande
- Necesita generators y automación
- Monorepo complejo con muchos proyectos
- Quiere grafo de dependencias avanzado
- Necesita affected commands precisos
Buenas Prácticas para Monorepos
1. Nomenclatura de Packages
// Usar scopes consistentes
{
"name": "@monorepo/ui", // Correcto
"name": "@mycompany/ui", // Correcto (scope de la empresa)
"name": "ui", // Evitar - puede conflictar con npm
"name": "my-ui-lib" // Evitar - difícil de identificar origen
}2. Configuraciones Compartidas
// packages/config/eslint/react.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
},
};
// Uso en apps/web/.eslintrc.js
module.exports = {
extends: [require.resolve('@monorepo/config/eslint/react')],
};// packages/config/typescript/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true
}
}
// Uso en apps/web/tsconfig.json
{
"extends": "@monorepo/config/typescript/nextjs.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}3. Versionamiento
// package.json
{
"devDependencies": {
"changeset": "^2.0.0"
},
"scripts": {
"changeset": "changeset",
"version": "changeset version",
"release": "changeset publish"
}
}# Flujo de cambios
# 1. Hacer cambios en packages
# 2. Crear changeset
npx changeset
# 3. Responder preguntas sobre el cambio
# 4. Antes del release, consumir changesets
npx changeset version
# 5. Publicar (si packages son públicos)
npx changeset publish4. CI/CD Optimizado
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Necesario para affected commands
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Turborepo con remote cache
- name: Build
run: pnpm build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
# O con Nx
- name: Build affected
run: npx nx affected:build --base=origin/main~1 --head=HEADConclusión
Monorepos son la arquitectura preferida para proyectos JavaScript de escala en 2025. Con Turborepo o Nx, los dolores tradicionales de gerenciar múltiples repositorios desaparecen.
Resumen:
- Turborepo: Simplicidad y velocidad
- Nx: Power features y extensibilidad
Ambos son excelentes elecciones. El importante es comenzar y iterar conforme las necesidades del equipo evolucionan.
Si quieres profundizar en arquitectura de proyectos JavaScript, recomiendo que veas otro artículo: Vite vs Webpack en 2025 donde vas a descubrir cuál build tool elegir para tu monorepo.

