Volver al blog

Monorepos con Nx y Turborepo: Cómo Grandes Empresas Gerencian Proyectos en 2025

Hola HaWkers, si ya trabajaste en una empresa con múltiples proyectos relacionados - un app mobile, un dashboard admin, un site público, una API - probablemente ya sentiste el dolor de mantener código duplicado, versiones desalineadas de bibliotecas compartidas, y deploys que parecen una orquesta sin maestro.

¿Ya paraste para pensar cómo empresas como Google, Facebook y Microsoft consiguen gerenciar centenas de proyectos que comparten código sin caer en el caos? Y más importante: ¿cómo puedes aplicar esas mismas estrategias en tus proyectos, incluso en equipos menores?

Qué Son Monorepos y Por Qué Importan en 2025

Monorepo es una estrategia de organización de código donde múltiples proyectos relacionados viven en el mismo repositorio Git. En vez de tener un repositorio para cada app, tienes un único repositorio conteniendo todos tus proyectos, compartiendo código, herramientas y pipelines.

En 2025, monorepos dejaron de ser una curiosidad de gigantes de la tecnología para tornarse mainstream. Las herramientas maduraron, la documentación mejoró, y los beneficios se tornaron imposibles de ignorar:

Ventajas claras:

  • Code sharing simplificado: Comparte bibliotecas entre proyectos sin publicar en npm
  • Refactorización atómica: Cambia una API y actualiza todos los consumidores en el mismo commit
  • Consistencia: Mismas versiones de dependencias, mismas herramientas, mismos padrones
  • Visibility: Ve cómo cambios afectan todos los proyectos
  • CI/CD optimizado: Ejecuta tests y builds apenas de lo que cambió

Desafíos resueltos en 2025:

  • Performance de herramientas (Nx y Turborepo son 7x+ más rápidos)
  • Complejidad de setup (templates prontos y documentación clara)
  • Escalabilidad (soporte a miles de proyectos)

Nx: La Solución Enterprise-Grade

Nx es una herramienta de monorepo poderosa, creada por Nrwl, focada en escalabilidad y experiencia del desarrollador. En 2025, Nx se consolidó como la opción para monorepos complejos y polyglot (múltiples lenguajes).

Características del Nx

  1. Task orchestration inteligente: Nx entiende dependencias entre proyectos y ejecuta tasks en el orden correcto
  2. Computation caching: Resultados de builds y tests son cacheados localmente y remotamente
  3. Affected commands: Ejecuta tasks apenas en proyectos afectados por cambios
  4. Plugins para todo: React, Angular, Node, Go, Rust, y más
  5. Dependency graph: Visualización interactiva de cómo proyectos se relacionan

Setup de un Monorepo Nx

# Crear nuevo workspace Nx
npx create-nx-workspace@latest my-workspace

# Escoge las opciones:
# - Package-based monorepo o Integrated monorepo
# - TypeScript/JavaScript
# - CI/CD provider (GitHub Actions, GitLab, etc)

cd my-workspace

# Añadir aplicaciones
nx g @nx/react:app web
nx g @nx/react:app admin
nx g @nx/node:app api

# Añadir bibliotecas compartidas
nx g @nx/js:lib shared-ui
nx g @nx/js:lib shared-utils
nx g @nx/js:lib data-access

Estructura de un Monorepo Nx

my-workspace/
├── apps/
│   ├── web/               # App React del site público
│   ├── admin/             # Dashboard administrativo
│   └── api/               # Backend Node.js
├── libs/
│   ├── shared-ui/         # Componentes UI compartidos
│   ├── shared-utils/      # Utilitarios compartidos
│   └── data-access/       # Cliente API compartido
├── tools/
│   └── generators/        # Generadores customizados
├── nx.json                # Configuración del Nx
├── package.json
└── tsconfig.base.json

Configuración nx.json

{
  "extends": "nx/presets/npm.json",
  "tasksRunnerOptions": {
    "default": {
      "runner": "nx/tasks-runners/default",
      "options": {
        "cacheableOperations": ["build", "test", "lint"],
        "parallel": 3
      }
    }
  },
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["{projectRoot}/dist"]
    },
    "test": {
      "cache": true
    }
  },
  "namedInputs": {
    "default": ["{projectRoot}/**/*"],
    "production": ["!{projectRoot}/**/*.spec.ts"]
  }
}

Trabajando con Bibliotecas Compartidas

// libs/shared-ui/src/lib/Button.tsx
import React from 'react';

export interface ButtonProps {
  variant?: 'primary' | 'secondary';
  onClick?: () => void;
  children: React.ReactNode;
}

export function Button({ variant = 'primary', onClick, children }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// libs/shared-ui/src/index.ts - Barrel export
export * from './lib/Button';
export * from './lib/Input';
export * from './lib/Modal';

Usando en apps:

// apps/web/src/app/app.tsx
import { Button } from '@my-workspace/shared-ui';

export function App() {
  return (
    <div>
      <h1>Welcome to Web App</h1>
      <Button variant="primary" onClick={() => console.log('Clicked')}>
        Click Me
      </Button>
    </div>
  );
}

Data Access Layer Compartido

// libs/data-access/src/lib/api-client.ts
export class ApiClient {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  async get<T>(endpoint: string): Promise<T> {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    if (!response.ok) {
      throw new Error(`API Error: ${response.statusText}`);
    }
    return response.json();
  }

  async post<T, D>(endpoint: string, data: D): Promise<T> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new Error(`API Error: ${response.statusText}`);
    }

    return response.json();
  }
}

// libs/data-access/src/lib/users-service.ts
import { ApiClient } from './api-client';

export interface User {
  id: string;
  name: string;
  email: string;
}

export class UsersService {
  constructor(private api: ApiClient) {}

  async getUsers(): Promise<User[]> {
    return this.api.get<User[]>('/users');
  }

  async getUser(id: string): Promise<User> {
    return this.api.get<User>(`/users/${id}`);
  }

  async createUser(user: Omit<User, 'id'>): Promise<User> {
    return this.api.post<User, Omit<User, 'id'>>('/users', user);
  }
}

// libs/data-access/src/index.ts
export * from './lib/api-client';
export * from './lib/users-service';

Uso en los apps:

// apps/web/src/services/index.ts
import { ApiClient, UsersService } from '@my-workspace/data-access';

const apiClient = new ApiClient(process.env.NX_API_URL || 'http://localhost:3333');
export const usersService = new UsersService(apiClient);

// apps/web/src/app/users-page.tsx
import { useEffect, useState } from 'react';
import { usersService } from '../services';
import { User } from '@my-workspace/data-access';

export function UsersPage() {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    usersService.getUsers()
      .then(setUsers)
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <h1>Users</h1>
      {users.map(user => (
        <div key={user.id}>
          {user.name} - {user.email}
        </div>
      ))}
    </div>
  );
}

Turborepo: Velocidad y Simplicidad

Turborepo, creado por Vercel, foca en simplicidad y performance extrema. Es más leve que Nx pero increíblemente eficiente para monorepos JavaScript/TypeScript.

Características del Turborepo

  1. Zero config cache: Cache distribuido con zero configuración
  2. Incremental builds: Solo rebuilda lo que cambió
  3. Remote caching: Comparte cache entre toda la equipe
  4. Lean: Filosofía minimalista, aprovecha herramientas existentes
  5. Vercel integration: Integración nativa con Vercel

Setup de un Monorepo Turborepo

# Crear nuevo monorepo
npx create-turbo@latest

cd my-turborepo

# Estructura generada:
# apps/
#   web/          # Next.js app
#   docs/         # Next.js docs site
# packages/
#   ui/           # Shared UI components
#   eslint-config/
#   tsconfig/
# turbo.json
# package.json

Configuración turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [".env"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "cache": true
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Workspace Package.json

{
  "name": "my-turborepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "test": "turbo run test",
    "lint": "turbo run lint"
  },
  "devDependencies": {
    "turbo": "latest"
  }
}

Shared Package UI

// packages/ui/src/Button.tsx
import * as React from 'react';

export interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;
  variant?: 'primary' | 'secondary';
}

export function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      className={`button button--${variant}`}
    >
      {children}
    </button>
  );
}

// packages/ui/package.json
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "main": "./src/index.tsx",
  "types": "./src/index.tsx",
  "license": "MIT",
  "scripts": {
    "lint": "eslint src/",
    "test": "jest"
  },
  "devDependencies": {
    "@repo/eslint-config": "*",
    "@repo/typescript-config": "*",
    "@types/react": "^18.2.0",
    "react": "^18.2.0"
  },
  "peerDependencies": {
    "react": "^18.2.0"
  }
}

// packages/ui/src/index.tsx
export * from './Button';
export * from './Input';
export * from './Card';

Remote Caching con Vercel

# Conectar al Vercel Remote Cache
npx turbo login

# Link al proyecto
npx turbo link

# Ahora todos los builds son cacheados en la nube
# Todo el equipo se beneficia del cache compartido
// turbo.json con remote cache
{
  "$schema": "https://turbo.build/schema.json",
  "remoteCache": {
    "signature": true
  },
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    }
  }
}

Nx vs Turborepo: Cuándo Usar Cada Uno

Usa Nx cuando:

  1. Proyectos polyglot: Precisas de soporte a múltiples lenguajes (TypeScript, Go, Rust, Python)
  2. Escala enterprise: Centenas de proyectos, múltiples teams
  3. Customización profunda: Precisas de generadores customizados, plugins específicos
  4. Dependency graph complejo: Quieres visualización y análisis profundo de dependencias
  5. Conformance rules: Precisas garantizar padrones entre toda organización

Usa Turborepo cuando:

  1. JavaScript/TypeScript only: Foco total en ecosistema JS
  2. Simplicidad: Quieres configuración mínima, aprovechar herramientas existentes
  3. Vercel ecosystem: Usas Next.js y deploys en Vercel
  4. Performance pura: Priorizas velocidad absoluta de build
  5. Equipo pequeño/medio: No precisas de toda complejidad del Nx
// Comparación de performance (proyecto medio con 20 apps)
const benchmarks = {
  nx: {
    coldBuild: '45s',
    cachedBuild: '2.1s',
    affectedBuild: '8.3s',
    features: ['affected', 'graph', 'plugins', 'generators']
  },
  turborepo: {
    coldBuild: '38s',
    cachedBuild: '1.8s',
    affectedBuild: '7.1s',
    features: ['cache', 'pipeline', 'remote-cache']
  }
};

console.table(benchmarks);
// Ambos son extremadamente rápidos, elección basada en features necesarias

Estrategias de Organización de Código

Boundary Rules (Nx)

// .eslintrc.json - Enforza límites entre módulos
{
  "overrides": [
    {
      "files": ["*.ts"],
      "rules": {
        "@nx/enforce-module-boundaries": [
          "error",
          {
            "allow": [],
            "depConstraints": [
              {
                "sourceTag": "type:app",
                "onlyDependOnLibsWithTags": ["type:feature", "type:ui", "type:util"]
              },
              {
                "sourceTag": "type:feature",
                "onlyDependOnLibsWithTags": ["type:ui", "type:util", "type:data-access"]
              },
              {
                "sourceTag": "type:ui",
                "onlyDependOnLibsWithTags": ["type:util"]
              }
            ]
          }
        ]
      }
    }
  ]
}

Tagging System

// libs/shared-ui/project.json
{
  "name": "shared-ui",
  "tags": ["type:ui", "scope:shared"]
}

// libs/web-feature-auth/project.json
{
  "name": "web-feature-auth",
  "tags": ["type:feature", "scope:web"]
}

// libs/data-access/project.json
{
  "name": "data-access",
  "tags": ["type:data-access", "scope:shared"]
}

Code Generators (Nx)

// tools/generators/component/index.ts
import { Tree, formatFiles, installPackagesTask } from '@nx/devkit';

export default async function (tree: Tree, schema: any) {
  const projectRoot = `libs/${schema.project}/src/lib`;

  tree.write(
    `${projectRoot}/${schema.name}.tsx`,
    `import React from 'react';

export interface ${schema.name}Props {
  children?: React.ReactNode;
}

export function ${schema.name}({ children }: ${schema.name}Props) {
  return (
    <div>
      <h1>${schema.name}</h1>
      {children}
    </div>
  );
}
`
  );

  tree.write(
    `${projectRoot}/${schema.name}.spec.tsx`,
    `import { render } from '@testing-library/react';
import { ${schema.name} } from './${schema.name}';

describe('${schema.name}', () => {
  it('should render successfully', () => {
    const { baseElement } = render(<${schema.name} />);
    expect(baseElement).toBeTruthy();
  });
});
`
  );

  await formatFiles(tree);
  return () => {
    installPackagesTask(tree);
  };
}

// Uso:
// nx g @my-workspace/tools:component MyComponent --project=shared-ui

CI/CD Optimizado para Monorepos

GitHub Actions con Nx

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'npm'

      - run: npm ci

      - uses: nrwl/nx-set-shas@v3

      - run: npx nx affected -t lint test build --parallel=3
        env:
          NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

GitHub Actions con Turborepo

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'npm'

      - run: npm ci

      - run: npx turbo run build test lint
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}

Mejores Prácticas Monorepo 2025

  1. Usa workspace protocol para dependencias internas
{
  "dependencies": {
    "@my-workspace/shared-ui": "workspace:*"
  }
}
  1. Versiona bibliotecas compartidas semánticamente
nx release version --skip-publish
nx release publish
  1. Configura paths en tsconfig.base.json
{
  "compilerOptions": {
    "paths": {
      "@my-workspace/shared-ui": ["libs/shared-ui/src/index.ts"],
      "@my-workspace/*": ["libs/*/src/index.ts"]
    }
  }
}
  1. Usa conventional commits para changelogs automáticos
git commit -m "feat(shared-ui): add new Button variant"
git commit -m "fix(api): resolve authentication bug"
  1. Implementa code owners
# CODEOWNERS
/libs/shared-ui/ @frontend-team
/apps/api/ @backend-team
/libs/data-access/ @full-stack-team

El Futuro de los Monorepos

En 2025, monorepos se consolidaron como la forma preferida de organizar código en empresas de todos los tamaños. Las herramientas están maduras, la comunidad es fuerte, y los beneficios son claros.

Lo que viene por ahí:

  1. IA-powered code generation: Generadores que entienden tu padrón y sugieren código
  2. Todavía más performance: Herramientas escritas en Rust/Go quedando todavía más rápidas
  3. Cloud development environments: Workspaces rodando en la nube con cache compartido
  4. Cross-language monorepos: Soporte todavía mejor para proyectos polyglot
  5. Automated dependency updates: IA que actualiza dependencias de forma segura

Monorepos no son silver bullet, pero para la mayoría de las organizaciones con múltiples proyectos relacionados, son la mejor opción en 2025. La cuestión no es más "si", pero "cuándo" hacer la transición.

Si quieres continuar profundizando en herramientas y prácticas modernas de desarrollo, recomiendo volver al inicio de esta serie: Mercado de Desarrollo de Software en 2025: Tendencias, Salarios y Skills en Alta Demanda donde tendrás una visión completa del mercado actual.

¡Vamos a por ello! 🦅

📚 ¿Quieres Profundizar Tus Conocimientos en JavaScript?

Este artículo cubrió monorepos y arquitectura de proyectos, pero hay mucho más para explorar en el mundo del desarrollo moderno.

Desarrolladores que invierten en conocimiento sólido y estructurado tienden a tener más oportunidades en el mercado.

Material de Estudio Completo

Si quieres dominar JavaScript del básico al avanzado, preparé una guía completa:

Opciones de inversión:

  • $9.90 USD (pago único)

👉 Conocer la Guía JavaScript

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios