Настройка Monorepo (Nx) для веб-проекта
Nx — это не просто build system с кешированием, а полноценная платформа для monorepo: генераторы кода, плагины для каждого фреймворка, граф зависимостей с визуализацией, возможность запускать только затронутые проекты. Подходит для больших команд и сложных проектов, где Turborepo будет ощущаться недостаточным.
Nx vs Turborepo: принципиальная разница
Turborepo — task runner с кешем. Он не знает про содержимое ваших пакетов.
Nx — понимает структуру: знает, что это Next.js приложение, что то — NestJS, может генерировать компоненты, модули, тесты по шаблонам. Плагин @nx/next настраивает правила кеширования, generatory, задачи — всё автоматически.
Турбо подходит, когда уже есть сложившийся стек и нужно только ускорить CI. Nx подходит, когда команда большая и нужно стандартизировать разработку.
Инициализация
# Новый workspace
npx create-nx-workspace@latest acme --preset=apps
cd acme
# Добавляем приложения
npx nx generate @nx/next:app web --directory=apps/web
npx nx generate @nx/react:app admin --directory=apps/admin --bundler=vite
npx nx generate @nx/node:app api --directory=apps/api --framework=express
# Добавляем библиотеки
npx nx generate @nx/react:lib ui --directory=packages/ui --component
npx nx generate @nx/js:lib utils --directory=packages/utils
npx nx generate @nx/js:lib types --directory=packages/types --bundler=none
Структура и nx.json
// nx.json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"lint": {
"cache": true,
"inputs": [
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore"
]
},
"test": {
"cache": true,
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
},
"typecheck": {
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/*.spec.*",
"!{projectRoot}/jest.config.*",
"!{projectRoot}/.eslintrc.json"
],
"sharedGlobals": []
},
"generators": {
"@nx/react": {
"component": {
"style": "none",
"classComponent": false
}
}
},
"defaultBase": "main"
}
project.json для каждого проекта
Nx использует project.json вместо scripts в package.json (хотя оба варианта поддерживаются):
// apps/web/project.json
{
"name": "web",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/web/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/next:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/apps/web"
},
"configurations": {
"development": {
"outputPath": "apps/web"
},
"production": {
"outputPath": "dist/apps/web"
}
}
},
"serve": {
"executor": "@nx/next:server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "web:build",
"dev": true
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/web/**/*.{ts,tsx}"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/web"],
"options": {
"jestConfig": "apps/web/jest.config.ts"
}
},
"typecheck": {
"executor": "nx:run-commands",
"options": {
"command": "tsc --noEmit",
"cwd": "apps/web"
}
}
},
"tags": ["type:app", "scope:web"]
}
Tags и lint правила для архитектурных ограничений
Одна из сильных сторон Nx — enforcement границ между модулями:
// .eslintrc.json (корневой)
{
"plugins": ["@nx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"depConstraints": [
{
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": ["type:lib", "type:util"]
},
{
"sourceTag": "scope:web",
"onlyDependOnLibsWithTags": ["scope:web", "scope:shared"]
},
{
"sourceTag": "scope:api",
"onlyDependOnLibsWithTags": ["scope:api", "scope:shared"]
},
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
}
]
}
]
}
}
Это значит: apps/web не может импортировать из apps/api, scope:web не может импортировать scope:api-библиотеки. Нарушение — ошибка линтера прямо в IDE.
Граф зависимостей
# Визуализация в браузере
npx nx graph
# Только для конкретного проекта
npx nx graph --focus=web
# Что затронуто изменениями в ветке
npx nx affected:graph
Граф помогает понять цену изменения: если меняешь packages/ui, сразу видишь какие приложения нужно пересобрать.
Запуск только affected проектов
# Тесты только для затронутых проектов
npx nx affected:test
# Сборка только изменившегося
npx nx affected:build
# С базой сравнения
npx nx affected:test --base=origin/main --head=HEAD
# Параллельно, не более 4 задач
npx nx affected:build --parallel=4
Генераторы: стандартизация разработки
Nx позволяет писать собственные генераторы — скрипты, которые создают файлы по шаблонам:
// tools/generators/feature/index.ts
import type { Tree } from '@nx/devkit';
import {
formatFiles,
generateFiles,
names,
offsetFromRoot,
} from '@nx/devkit';
import * as path from 'path';
interface FeatureGeneratorSchema {
name: string;
project: string;
}
export default async function featureGenerator(
tree: Tree,
options: FeatureGeneratorSchema
) {
const projectRoot = `apps/${options.project}/src/features`;
const { fileName, className } = names(options.name);
generateFiles(
tree,
path.join(__dirname, 'files'), // шаблоны
`${projectRoot}/${fileName}`,
{
className,
fileName,
offsetFromRoot: offsetFromRoot(projectRoot),
}
);
await formatFiles(tree);
}
# Запуск генератора
npx nx generate @acme/tools:feature --name=user-profile --project=web
Генератор создаёт все нужные файлы: компонент, тест, сторсfile, index.ts для реэкспорта — по заранее утверждённому шаблону.
Nx Cloud: distributed task execution
Nx Cloud позволяет распределить выполнение задач между несколькими агентами CI:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
agents:
name: Nx Cloud Agent ${{ matrix.agent }}
runs-on: ubuntu-latest
strategy:
matrix:
agent: [1, 2, 3]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx nx-cloud start-agent
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
main:
runs-on: ubuntu-latest
needs: []
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js"
- run: npx nx affected -t lint typecheck test build
- run: npx nx-cloud stop-all-agents
if: always()
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
При 20+ проектах DTE сокращает CI с 30 минут до 7–10.
Миграция с Turborepo
Если уже используете Turbo:
# Nx умеет импортировать существующий workspace
npx nx@latest init
# Следуем инструкциям — Nx определит структуру и добавит свои конфиги
Сроки
Настройка Nx с нуля для 6–10 проектов — три-пять дней: создание workspace, настройка плагинов, module boundaries, генераторов, CI/CD. Миграция существующего monorepo — неделя-полторы в зависимости от числа кастомных конфигов сборки.







