El proyecto empieza con un tsconfig.json en la raíz. Después aparecen una biblioteca compartida, una API, una aplicación web y pruebas que no deberían entrar en el mismo build. El compilador sigue viendo una carpeta grande, el editor abre archivos de proyectos distintos y el CI ejecuta un comando que no representa la estructura real del repositorio.

Project References crean esa frontera. Convierte cada parte en un proyecto de TypeScript compilable, declara las dependencias en tsconfig.json y usa tsc -b para descubrir el orden. La función no sustituye a npm workspaces ni justifica reorganizar cualquier repositorio pequeño.

Esta guía empieza con un monorepo mínimo y termina con criterios para decidir si la configuración merece mantenerse. El código es ilustrativo. No se ejecutó en un proyecto de producción. Los comandos de verificación muestran cómo probar el mismo esquema en tu repositorio.

Diagrama que muestra un tsconfig dividido en proyectos core y api conectados por tsc -b.

Respuesta corta

  • Usa Project References cuando tu repositorio tenga proyectos que necesiten fronteras, builds separados u orden explícito de dependencias.
  • Activa composite en los proyectos referenciados, genera declaraciones y conserva un tsconfig de solución con files: [].
  • Ejecuta tsc -b desde la raíz. El tsc normal no orquesta el grafo de referencias de la misma manera.
  • Si el código es pequeño y no tiene una frontera real entre proyectos, varios tsconfig pueden añadir más trabajo que valor.

¿Qué necesitas antes de empezar?

Necesitas una versión LTS mantenida de Node.js, npm, un terminal y conocimientos básicos de package.json y tsconfig.json. El ejemplo usa npm workspaces, pero la idea del grafo también sirve para pnpm, Yarn o directorios que no se publican como paquetes. Instala TypeScript en el workspace antes de ejecutar el script:

npm install --save-dev typescript @types/node

El objetivo es terminar con dos proyectos compilables, un directorio dist por proyecto y una configuración de solución que puedas ejecutar desde la raíz. Si tu repositorio usa un bundler, separa el typecheck del comando que empaqueta JavaScript.

¿Cuándo merece la pena configurar Project References?

TypeScript introdujo Project References en la versión 3.0 para dividir los programas en partes más pequeñas y trabajar con el modo --build (TypeScript, “Project References”, consultado el 24/08/2026). La función merece la pena cuando la división representa una dependencia real, no solo una preferencia por carpetas más pequeñas.

Busca estas señales:

  • una biblioteca debe compilarse antes de que una API pueda consumirla;
  • las pruebas y el código de producción tienen entradas, salidas u opciones diferentes;
  • el editor y el CI necesitan ver las mismas fronteras;
  • un cambio en un paquete no debería reprocesar todos los demás;
  • el equipo quiere que los archivos .d.ts sean la frontera pública de tipos de un proyecto.

No leas “builds más rápidos” como un benchmark. La documentación oficial explica cómo el modo de build comprueba qué proyectos están actualizados y compila los que necesitan trabajo, pero el resultado depende del grafo, el bundler y la cantidad de código. Mide tu repositorio cuando la configuración ya sea correcta.

La pregunta útil no es “¿cuántos paquetes tenemos?”. Es “¿qué proyecto debería poder compilar sin abrir los detalles internos del otro?”. Si puedes nombrar al menos una frontera, Project References puede expresar una decisión de arquitectura. Si nadie puede nombrarla, el problema sigue siendo de organización, no de tsconfig.

¿Qué resuelven npm workspaces y qué no resuelven?

npm define workspaces como funciones para gestionar varios paquetes locales desde un paquete raíz. npm install crea los enlaces locales sin exigir llamadas manuales a npm link (npm, “Workspaces”, consultado el 24/08/2026). Eso resuelve la instalación y la resolución local de paquetes. No describe el orden de compilación de TypeScript.

Piensa en estas responsabilidades como dos capas:

Capa Pregunta Función
Paquetes ¿Cómo aparece @acme/core en el node_modules local? npm workspaces
Compilación ¿Qué debe construirse antes de apps/api? Project References
Tipos ¿Qué salida representa la frontera de core? declaration y .d.ts
Orquestación ¿Qué comando descubre el orden y omite proyectos actualizados? tsc -b

Un workspace puede enlazar core con api mientras el compilador sigue tratando ambos como una sola lista de archivos. También es posible usar Project References para organizar directorios sin npm workspaces. Combinar ambos es habitual porque uno describe las relaciones entre paquetes y el otro los proyectos de TypeScript.

Si estás empezando por la guía para crear un servidor MCP en TypeScript, probablemente baste una configuración. Cuando el servidor empieza a compartir código con otro paquete, la pregunta pasa a ser cuál es la frontera de compilación.

¿Cómo montar un monorepo mínimo de TypeScript?

Empieza con una biblioteca y una aplicación. Así el grafo queda visible y no introduces React, un bundler o una herramienta de monorepo antes de entender qué está haciendo el compilador.

.
├── package.json
├── tsconfig.json
├── packages/
│   └── core/
│       ├── package.json
│       ├── tsconfig.json
│       └── src/index.ts
└── apps/
    └── api/
        ├── package.json
        ├── tsconfig.json
        └── src/index.ts

En el package.json de la raíz, declara los workspaces y deja visible el comando de build:

{
  "name": "acme-workspace",
  "private": true,
  "workspaces": ["packages/*", "apps/*"],
  "scripts": {
    "build": "tsc -b tsconfig.json",
    "build:verbose": "tsc -b tsconfig.json --verbose"
  }
}

En el paquete core, configura la salida que leerá su consumidor:

{
  "name": "@acme/core",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts"
}

El paquete de la API puede depender del nombre del workspace:

{
  "name": "@acme/api",
  "version": "1.0.0",
  "dependencies": {
    "@acme/core": "1.0.0"
  }
}

Añade una función pequeña en packages/core/src/index.ts para que la API tenga una superficie pública concreta:

export function formatServiceName(service: string): string {
  return service.toUpperCase();
}

npm documenta que una dependencia de workspace se enlaza localmente cuando el paquete aparece en la configuración del workspace (npm, “Adding dependencies to a workspace”, consultado el 24/08/2026). El enlace local permite encontrar el paquete. La referencia en tsconfig sigue siendo necesaria para que tsc -b conozca el orden.

¿Cómo declarar las referencias y ejecutar tsc -b?

Un proyecto referenciado necesita composite: true, y la raíz puede funcionar como una solución sin archivos propios. La documentación de TypeScript también recomienda una configuración con files: [] que apunte a los proyectos hoja (TypeScript, “Project References”, consultado el 24/08/2026).

Crea primero la configuración de solución:

{
  "files": [],
  "references": [
    { "path": "./packages/core" },
    { "path": "./apps/api" }
  ]
}

El proyecto core necesita sus propias entradas, salidas y declaraciones:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "rootDir": "src",
    "outDir": "dist"
  },
  "include": ["src/**/*.ts"]
}

En apps/api/tsconfig.json, repite las opciones que deban mantenerse consistentes y añade la dependencia de proyecto:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "rootDir": "src",
    "outDir": "dist"
  },
  "include": ["src/**/*.ts"],
  "references": [
    { "path": "../../packages/core" }
  ]
}

La API puede importar el paquete por su nombre:

import { formatServiceName } from "@acme/core";

console.log(formatServiceName("billing"));

Después de instalar las dependencias y comprobar que TypeScript está disponible, ejecuta:

npm install
npx tsc -b tsconfig.json --verbose

El modo build encuentra los proyectos referenciados, comprueba cuáles están actualizados y construye las dependencias en el orden correcto. También puede recibir la ruta de un proyecto, como npx tsc -b apps/api.

El ejemplo es pequeño a propósito. En un proyecto real, centraliza las opciones compartidas con extends, pero mantén coherentes rootDir, outDir y los archivos incluidos en cada paquete. Un tsconfig.base.json compartido no debe ocultar la dirección de las dependencias.

¿Qué cambia al activar composite, declaration y declarationMap?

composite añade restricciones que ayudan al compilador a determinar rápidamente si un proyecto ya se construyó. El manual de TypeScript explica que los archivos de implementación deben estar cubiertos por include o files, que rootDir tiene otros valores predeterminados y que declaration se activa para un proyecto referenciado (TypeScript, “Project References”, consultado el 24/08/2026).

Estas opciones forman parte del contrato:

  • composite indica que el proyecto puede participar en un grafo de build;
  • declaration genera archivos .d.ts para la superficie tipada del consumidor;
  • declarationMap conecta las declaraciones con el código para funciones como Go to Definition;
  • outDir da a cada proyecto su propia salida y evita colisiones;
  • tsBuildInfoFile, usado por configuraciones incrementales, guarda información para builds posteriores.

El TSConfig Reference de declarationMap recomienda considerar esta opción cuando usas Project References. No puede corregir un grafo equivocado. Si api referencia core pero importa archivos internos mediante un alias que ignora la frontera del paquete, la declaración generada no resolverá el problema arquitectónico.

Hay un coste práctico. Después de clonar el repositorio, los .d.ts esperados pueden no existir. La documentación de TypeScript advierte que quizá debas construir el proyecto o versionar ciertas salidas para que el editor navegue sin errores falsos. Decide esta política como parte del flujo de desarrollo, no después del primer error en el equipo.

Este artículo no tiene una prueba de producción que elija entre versionar dist y generarlo después de clonar. Esa decisión depende del bundler, el entorno y la política del repositorio. El punto verificable es más sencillo: un clon limpio debe poder generar las declaraciones antes de que una herramienta dependiente intente resolver el paquete.

¿Cómo comprobar que el grafo es correcto?

La configuración está lista cuando el comando de build demuestra la relación que declaran los archivos. Ejecuta un build detallado y lee qué proyectos estaban actualizados, cuáles se reconstruyeron y cuáles se omitieron:

npx tsc -b tsconfig.json --verbose --pretty false

Para simular la decisión sin escribir las salidas, usa --dry si la versión de TypeScript instalada lo admite:

npx tsc -b tsconfig.json --dry --verbose

Después de la primera ejecución, revisa estas señales:

  1. Existe packages/core/dist/index.d.ts y representa los exports públicos.
  2. apps/api compila sin importar packages/core/src mediante una ruta relativa.
  3. Cambiar solo core hace que el build identifique api como dependiente cuando cambia la salida pública.
  4. Quitar una entrada de references produce un error que muestra la dependencia ausente.
  5. CI usa el mismo script de la raíz en lugar de un tsc --noEmit que solo carga un proyecto.

Si el paquete usa un bundler, separa las tareas. El bundler puede generar el JavaScript de la aplicación mientras tsc -b comprueba las fronteras y las declaraciones. Un build correcto del bundler no demuestra que el grafo de Project References esté bien.

Para una biblioteca consumida por otros paquetes, revisa también cómo publicas los tipos. La guía para servir definiciones TypeScript de Eden Treaty trata una frontera de paquete publicado. Aquí el foco es una frontera interna, pero ambos problemas se encuentran cuando el .d.ts local se convierte en el contrato que importa otro consumidor.

¿Por qué pueden discrepar el editor y el CI?

El editor puede abrir un archivo usando el tsconfig más cercano, mientras CI ejecuta un comando desde la raíz. Project References hacen explícito el grafo, pero no obligan a todas las herramientas a usar el mismo programa. TypeScript advierte que los proyectos dependientes usan declaraciones construidas y que tsc solo construye dependencias automáticamente cuando recibe --build (TypeScript, “Caveats for Project References”, consultado el 24/08/2026).

Los síntomas habituales son:

  • el editor navega hasta un tipo, pero tsc --noEmit no encuentra el paquete;
  • la aplicación funciona después de un build manual, pero falla en un clon limpio;
  • el lint con información de tipos lee fuentes mientras el compilador lee .d.ts generados;
  • un alias de paths apunta a src y evita la salida del paquete;
  • el proyecto está referenciado, pero no tiene composite, include u outDir compatibles.

El issue sobre tipos que se resuelven como any entre paquetes con Project References es un registro útil para diagnosticar este tipo de fricción. No es una regla para todos los proyectos. Sirve para recordar que editor, linter, workspace y compilador pueden resolver las fronteras de maneras diferentes.

Cuando una herramienta no entiende el grafo, elige una estrategia explícita: configura la herramienta para leer los proyectos correctos, ejecuta el build antes del análisis que depende de declaraciones o conserva una configuración de typecheck separada. No elimines las referencias solo para que pase un comando aislado sin entender qué estaba comprobando.

¿Cuándo no conviene usar Project References?

No uses Project References solo porque el repositorio tenga dos carpetas. Si hay una aplicación pequeña, un proceso de build y ninguna frontera que el equipo quiera proteger, un solo tsconfig.json puede ser más claro.

Aplaza la adopción cuando:

  • el bundler todavía no puede consumir las nuevas salidas o aliases;
  • los paquetes no tienen una relación de dependencia que alguien pueda explicar;
  • el equipo no tiene un comando de clon limpio que genere las declaraciones necesarias;
  • la única motivación es copiar la configuración de un monorepo mucho mayor;
  • el problema real es un módulo mal organizado, no el alcance o la duración del typecheck.

Usa Project References cuando la frontera siga siendo útil aunque no mejore el tiempo. Una biblioteca que no debería importar la aplicación, un proyecto de pruebas con sus propias opciones o un paquete publicado internamente pueden justificar el grafo. Los builds incrementales son una posible consecuencia, no la única razón.

Si la arquitectura todavía no está clara, empieza por separar servicios y responsabilidades en el código. La guía de arquitectura de servicios con TypeScript trabaja en otro nivel. Project References hacen verificable una división existente, pero no crean una buena división por sí solas.

Preguntas frecuentes

¿Project References funcionan sin un monorepo?

Sí. La documentación de TypeScript describe referencias entre directorios que tienen sus propios archivos tsconfig.json. Un monorepo con workspaces es una combinación frecuente, pero no es un requisito. Necesitas proyectos separados de TypeScript y una dependencia que quieras declarar.

¿Puedo usar tsc -p en vez de tsc -b?

Puedes usar tsc -p para compilar un proyecto, pero no es el orquestador del grafo. Para construir dependencias en orden y reutilizar el estado conocido por el modo build, usa tsc -b con la configuración de solución. Compara ambos comandos con --verbose antes de elegir el script del CI.

¿Tengo que versionar los archivos .d.ts y dist?

No hay una respuesta universal. TypeScript advierte que los proyectos dependientes necesitan declaraciones para navegar y compilar. Puedes generarlas después de clonar, versionarlas o dejar que el entorno de desarrollo se encargue. Elige una política y haz que CI detecte un clon sin las salidas.

¿Project References sustituyen a Turborepo, Nx u otra herramienta de build?

No. Describen el grafo que entiende TypeScript. Una herramienta de build puede gestionar caché, tareas, bundling, pruebas y ejecución entre paquetes. Empieza con la función nativa cuando el problema sea la frontera de compilación. Añade otra capa cuando exista una necesidad que tsc -b no cubra.

Conclusión

Project References le dicen a TypeScript que el repositorio contiene proyectos, no solo carpetas. Workspaces enlaza paquetes locales. references declara quién depende de quién. composite y las declaraciones hacen que la frontera sea compilable. tsc -b demuestra el orden y encuentra qué debe reconstruirse.

Empieza con dos partes y un grafo pequeño. Ejecuta el build desde un clon limpio, revisa los .d.ts, alinea editor y CI y solo después divide más proyectos. Si la frontera no explica una decisión real del código, la configuración todavía no ha resuelto el problema que te llevó a buscarla.

Fuentes consultadas