Монорепозиторий для веба: Turborepo, Nx и pnpm workspaces
Продвинутое3 минРедакция Innovialabs
Коротко
Монорепозиторий хранит сайт, админку, API и общие пакеты в одном репозитории; pnpm workspaces связывают пакеты, а Turborepo или Nx ускоряют сборку за счет кэша и запуска только затронутых задач.
Что дает монорепозиторий
Монорепозиторий — это один Git-репозиторий, в котором живут несколько приложений и библиотек. Типичный веб-проект: публичный сайт, админка, API и общие пакеты — UI-компоненты, типы, конфиги.
Главные выгоды:
- Общие типы между фронтендом и API. Поменяли поле в модели — TypeScript сразу покажет все места, которые сломались.
- Одна дизайн-система для сайта и админки без публикации пакетов в npm.
- Атомарные изменения: правка API и клиента в одном коммите и одном pull request.
- Единые правила: один набор линтеров, форматирования и версий зависимостей.
Цена — инструменты: без них сборка и тесты всех пакетов при каждом изменении становятся медленными.
Пример структуры
my-project/
├── apps/
│ ├── web/ # публичный сайт (Next.js)
│ ├── admin/ # админка
│ └── api/ # бэкенд (Node.js)
├── packages/
│ ├── ui/ # общие компоненты
│ ├── types/ # общие типы и схемы
│ └── config/ # eslint, tsconfig
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
Правило: приложения зависят от пакетов, но не друг от друга. Если админке нужен код сайта — вынесите его в packages/.
Слой 1: pnpm workspaces
pnpm workspaces связывают пакеты внутри репозитория. Достаточно указать, где они лежат:
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
И подключить локальный пакет в приложении:
{
"dependencies": {
"@acme/ui": "workspace:*"
}
}
pnpm создает ссылку на локальную папку, поэтому изменения в packages/ui сразу видны в apps/web. Сам по себе pnpm не знает, что пересобирать и что кэшировать, — этим занимаются оркестраторы.
Слой 2: оркестратор задач и кэш
Оркестратор понимает граф зависимостей между пакетами и умеет:
- запускать задачи (
build,test,lint) в правильном порядке и параллельно; - кэшировать результаты: если входные файлы пакета не изменились, результат берется из кэша, а не считается заново;
- запускать задачи только для затронутых изменениями пакетов;
- делиться кэшем между разработчиками и CI через удаленный кэш.
Пример конфигурации Turborepo:
{
"$schema": "https://turborepo.com/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {},
"test": { "dependsOn": ["^build"] }
}
}
^build значит «сначала собери зависимости этого пакета». Формат конфигурации меняется между мажорными версиями, поэтому сверяйтесь с документацией вашей версии.
Turborepo, Nx или только pnpm
| Критерий | pnpm workspaces | Turborepo | Nx |
|---|---|---|---|
| Что это | Менеджер пакетов | Легкий оркестратор задач | Полноценная платформа для монорепозиториев |
| Кэш задач | Нет | Да, локальный и удаленный | Да, локальный и удаленный |
| Порог входа | Минимальный | Низкий | Выше |
| Генераторы кода, плагины | Нет | Минимально | Много |
| Контроль границ между пакетами | Нет | Ограниченно | Есть правила зависимостей |
| Кому подходит | 2–3 пакета, простые скрипты | Большинство JS/TS-проектов | Крупные репозитории, много команд |
Практичный путь: начать с pnpm workspaces, добавить Turborepo, когда сборка станет заметно медленной, и смотреть на Nx, если нужны строгие границы, генераторы и большой масштаб.
Частые ошибки
- Циклические зависимости между пакетами:
uiимпортирует изtypes, аtypes— изui. - Импорт из соседнего приложения вместо выноса кода в общий пакет.
- Неверные
outputsв настройках кэша: кэш восстанавливает не те файлы или не восстанавливает ничего. - Переменные окружения не учтены в кэше: сборка с другими значениями берет старый результат.
- Разные версии одной библиотеки в разных приложениях без причины.
- Секреты в общих пакетах: код из
packages/может попасть в клиентский бандл.