Qué es Nx y el project graph
Nx no es un lanzador de scripts con caché: es un build system que modela tu repositorio entero como un grafo. Antes de compilar nada infiere qué proyectos existen, cómo dependen unos de otros y qué tareas puede ejecutar cada uno. Ese grafo es el sustrato del que se derivan el affected, el orden de ejecución, la caché y la paralelización.
Nx no es un lanzador de scripts con caché pegada encima; es un build system que modela tu repositorio entero como un grafo. Antes de compilar, testear o desplegar nada, Nx construye una representación de qué proyectos existen, cómo dependen unos de otros y qué tareas puede ejecutar cada uno. Esa estructura —el project graph— es el sustrato del que se derivan todas sus capacidades: saber qué se ve afectado por un cambio, en qué orden ejecutar las tareas, qué puede cachearse y qué puede correr en paralelo. Entender Nx es, ante todo, entender ese grafo.
- Distinguir Nx como build system completo frente a un simple runner de scripts.
- Leer el project graph: proyectos como nodos, dependencias como aristas dirigidas.
- Entender cómo Nx infiere el grafo desde los imports y la configuración, sin listas a mano.
- Derivar el task graph del project graph y ver por qué es la base de todo el sistema.
De runner de scripts a build system
En un repositorio pequeño ejecutas npm run build y ya está: un package.json, un script, una tarea. El problema aparece con la escala. En un monorepo con decenas o cientos de proyectos, construir deja de ser una acción y pasa a ser un plan: qué proyectos hay que construir, en qué orden, cuáles dependen de cuáles, cuáles saltarse porque no cambiaron. Un runner ingenuo ejecuta todo, siempre y en serie. Nx sustituye ese enfoque por un modelo.
La diferencia central es que Nx entiende la estructura de tu repositorio, no solo los comandos que le pides. Sabe que la librería ui es dependencia de la app web, que web no depende de docs, y que un cambio en ui obliga a reconstruir web pero no docs. Ese conocimiento no lo mantienes tú en un archivo: Nx lo deriva analizando el código y la configuración de cada proyecto. Por eso se llama build system y no task runner: la unidad de trabajo no es el script suelto, es el grafo.
El contraste se ve mejor en la terminal. Sin un build system, encadenar compilaciones en el orden correcto obliga a escribir la secuencia a mano y a mantenerla cada vez que cambian las dependencias. Con Nx describes la relación una sola vez y el orden se deduce del grafo en cada ejecución.
# Sin build system: ejecutas todo, siempre, en el orden que adivines
npm run build -w util && npm run build -w ui && npm run build -w web
# Con Nx: pides el objetivo y el grafo decide el orden y que saltarse
nx build web
Nx no reemplaza a Vite, a tsc ni a Vitest. No compila tu TypeScript ni empaqueta tu app: eso lo siguen haciendo tus herramientas. Lo que Nx aporta es la capa por encima —qué ejecutar, cuándo, en qué orden, qué reutilizar de la caché— para que esas herramientas se invoquen el mínimo número de veces y en el orden correcto. Es un orquestador de tareas guiado por un grafo, no un compilador.
El project graph: el mapa del repo
El project graph es un grafo dirigido. Cada nodo es un proyecto: una app o una librería. Cada arista es una dependencia: si web importa de ui, hay una arista dirigida de web a ui. La dirección importa —codifica quién depende de quién— y es lo que permite responder preguntas como “si toco util, ¿qué se rompe aguas abajo?”.
flowchart TD web[app web] --> ui[lib ui] web --> data[lib data access] admin[app admin] --> ui ui --> util[lib util] data --> util style util fill:#cba6f7,color:#11111b style web fill:#a6e3a1,color:#11111b style admin fill:#a6e3a1,color:#11111b
Puedes explorar este grafo de forma interactiva con nx graph, que abre una visualización navegable, o listar los proyectos con nx show projects. No es un diagrama decorativo: es la misma estructura que Nx consulta internamente para cada decisión. Cuando ejecutas nx affected, Nx recorre estas aristas desde los archivos que cambiaste hacia todos los proyectos que dependen de ellos, directa o transitivamente, y solo esos entran en la ejecución.
Una fuente habitual de confusión es el sentido de las flechas. Una arista de web a ui significa web depende de ui, no al revés. Por eso las hojas del grafo —los nodos sin flechas salientes— son las librerías base, y las raíces son las apps que nadie consume. Cuando pienses en la propagación de un cambio, recórrelo en sentido contrario a las flechas: un cambio en ui afecta a todo lo que apunta hacia ui, que es justo lo que calcula nx affected.
Cómo Nx infiere el grafo
La propiedad más valiosa del project graph es que no lo escribes tú: Nx lo infiere. Lo construye a partir de tres fuentes.
Dependencias explícitas
Analiza estáticamente los import y require entre proyectos, más las dependencias declaradas en cada package.json. Si el código de un proyecto referencia a otro, la arista existe.
Dependencias implícitas
Cuando no hay enlace en el código —un proyecto de tests end to end que prueba una app— las declaras a mano con implicitDependencies en la configuración del proyecto.
Plugins de inferencia
Los plugins aportan nodos, aristas y targets leyendo archivos de configuración como vite.config.ts, sin que tú escribas nada. Lo verás en detalle en el nivel de plugins.
La configuración de cada proyecto vive en su project.json o directamente en su package.json, y la configuración global del workspace en nx.json, en la raíz. Nx cachea el grafo calculado y lo recomputa de forma incremental solo cuando cambian los archivos que lo afectan, de modo que consultarlo es casi instantáneo incluso en repositorios enormes.
Las dependencias implícitas se declaran en la configuración del proyecto que las necesita. El caso canónico es un proyecto de tests end to end: su código no importa el de la app que prueba, pero conceptualmente depende de ella, así que la arista hay que ponerla a mano para que el grafo no mienta.
// project.json: una arista que el analisis estatico no puede ver
{
"name": "web-e2e",
"implicitDependencies": ["web"]
}
Inspeccionar el grafo desde la terminal
No hace falta abrir la visualización para interrogar el grafo. La familia de comandos nx show responde en la terminal y en formato JSON, lo que la vuelve ideal para scripts y para depurar en CI. Con ella confirmas qué proyectos existen y qué targets y dependencias ve Nx en cada uno, sin depender de tu memoria de cómo está montado el repo.
# Lista todos los proyectos del workspace
nx show projects
# Todo lo que Nx sabe de un proyecto, en JSON
nx show project web --json
# Exporta el grafo a un archivo para inspeccionarlo o versionarlo
nx graph --file=graph.json
Nx deriva las dependencias del código leyendo los imports estáticos. Un import() con una ruta construida dinámicamente, o una dependencia expresada solo por convención en tiempo de ejecución, puede quedar invisible para el análisis. Ese es exactamente el hueco que cubre implicitDependencies: le dices a Nx la arista que no puede ver por sí mismo, para que el grafo siga siendo un reflejo fiel de la realidad. Un grafo incompleto produce un affected incompleto, y un affected incompleto es la causa raíz de “en mi máquina pasaba y en CI se rompió”.
Este hábito —preguntarle al grafo en vez de suponer— previene la clase de error más cara en un monorepo: creer que dos proyectos están desacoplados cuando comparten una dependencia transitiva. El grafo no opina ni recuerda mal; refleja lo que el código dice hoy, y por eso es la fuente de verdad a la que acudir cuando la intuición sobre la estructura del repo y la realidad empiezan a divergir.
Del project graph al task graph
El project graph responde “qué depende de qué entre proyectos”. Pero Nx no ejecuta proyectos: ejecuta tareas. Por eso deriva un segundo grafo, el task graph, cuyos nodos son tareas concretas —un par proyecto más target, como web:build— y cuyas aristas expresan qué tarea debe terminar antes que otra.
Ese task graph nace del project graph más la configuración de cada target. La regla más común es que el build de un proyecto dependa del build de sus dependencias: para construir web primero debe existir ui, y para ui primero util. Nx resuelve ese orden con un ordenamiento topológico y ejecuta en paralelo todas las tareas que no dependen entre sí.
flowchart TD t1[web:build] --> t2[ui:build] t1 --> t3[data:build] t2 --> t4[util:build] t3 --> t4 style t1 fill:#a6e3a1,color:#11111b style t4 fill:#cba6f7,color:#11111b
Fíjate en la correspondencia: el task graph tiene la misma forma que el project graph, pero traducida a acciones. Aguas abajo, en las hojas, están las librerías base que hay que construir primero; en la cima, las apps que las consumen. Este grafo de tareas es lo que Nx recorre para paralelizar, para cachear y —con Nx Cloud— para repartir el trabajo entre varias máquinas.
Conviene no confundir los dos grafos, porque responden a preguntas distintas y se recalculan en momentos distintos.
| Aspecto | Project graph | Task graph |
|---|---|---|
| Nodos | proyectos: apps y libs | tareas: proyecto mas target |
| Aristas | quien depende de quien | que tarea va antes que cual |
| Se deriva de | imports, package.json, plugins |
project graph mas dependsOn |
| Responde | que se ve afectado por un cambio | en que orden ejecutar |
El project graph es estable entre ejecuciones —cambia solo si cambian las dependencias—, mientras que el task graph se construye para cada invocación concreta a partir de qué targets pediste y de la configuración de sus dependencias. Uno es el mapa del territorio; el otro, la ruta que trazas sobre él para un viaje concreto.
Cuando la gente evalúa Nx suele fijarse en sus funciones visibles —la caché, el affected, los generadores, la distribución en la nube— y las trata como una lista de prestaciones sueltas que compite con las de Turborepo o con un puñado de scripts propios. Es el marco equivocado. Ninguna de esas funciones es primitiva: todas son teoremas que se demuestran a partir de un único axioma, el grafo. La caché existe porque Nx puede identificar una tarea por el hash de sus entradas, y sabe cuáles son sus entradas porque el grafo se lo dice. El affected existe porque Nx puede recorrer las aristas desde un cambio hasta todo lo que lo consume. El orden de ejecución existe porque el task graph es un DAG que admite un orden topológico. La distribución entre máquinas existe porque el grafo permite trocear el trabajo respetando las dependencias. Quita el grafo y no queda ninguna de esas capacidades; conserva el grafo y todas ellas caen por su propio peso. Por eso el primer trabajo real al adoptar Nx no es configurar la caché ni instalar plugins, sino garantizar que el grafo es verdadero: que toda dependencia real del código está representada por una arista, sin fantasmas ni huecos. Un grafo fiel convierte a Nx en un sistema que razona sobre tu repositorio; un grafo mentiroso lo convierte en un generador de builds sutilmente incorrectas que pasan en verde. La destreza con Nx se mide, antes que por cuántas funciones dominas, por cuánto cuidas la fidelidad de ese grafo.
- Ejecuta
nx graphen un monorepo y localiza las hojas —las librerías de las que todo depende— y las raíces —las apps que no consume nadie—. - Cambia una línea en una librería base y ejecuta
nx affected -t build --dry-runpara ver exactamente qué proyectos entran y cuáles quedan fuera. - Busca en el grafo un proyecto que sospeches que depende de otro por convención pero sin import: comprueba si la arista aparece o falta.
- Si falta, añade la arista con
implicitDependenciesy vuelve a mirar el grafo para confirmar que ahora es fiel a la realidad. - Compara el task graph de
buildcon el project graph y verifica que tienen la misma forma, traducida de proyectos a tareas.