Привет, Хабр! На связи Никита Ли, frontend-разработчик в Рунити.

За последние годы мы в Рунити пришли к довольно привычной для крупных frontend-команд ситуации: проектов становилось больше, кодовая база разрасталась, а количество переиспользуемых пакетов и микрофронтендов росло слишком быстро. Поддерживать зоопарк репозиториев становилось всё сложнее — и по времени, и по нервам.

В этой статье расскажу,  с какими проблемами мы столкнулись, зачем вернулись к одному репозиторию и что Nx реально изменил в нашей работе.

Навигация по тексту:

С чем мы столкнулись при внедрении микрофронтендов

Наш рабочий флоу стал выглядеть примерно так, и для того чтобы обновить размер шрифта или иконку, приходилось тратить до трёх дней — просто в ожидании апрувов от всех причастных.

Путь обновления кнопки в полирепо
Путь обновления кнопки в полирепо

Были и другие, не менее значимые проблемы, с которыми мы с командой разработки сталкивались практически ежедневно:

  • Кошмарный клубок зависимостей. Половина микрофронтендов сидит на устаревшей бета-версии пакета, в котором уже произошло несколько breaking changes, но обновлять и актуализировать код никто не спешит.

  • Конфиги, конфиги, конфиги. Они заполоняют корни репозиториев дублями: webpack.config.js, eslint.config.js, tsconfig.json, gitlab-ci.yml и так далее. Попробуйте добавить новое правило в ESLint без танцев с бубном.

  • Пик DX. Чтобы разрабатывать микрофронтенд, его нужно пересобрать локально и залинковать. После — дождаться CI, который вне зависимости от характера изменений прогоняется везде и всегда.

Когда проекты живут в отдельных репозиториях, любая общая логика довольно быстро превращается в источник постоянной боли. Общие пакеты приходится обновлять вручную, следить за версиями и цепочкой зависимостей, синхронизировать изменения. Чем теснее связаны проекты между собой, тем дороже становится любое изменение.

Особенно это чувствуется в микрофронтенд-архитектуре, когда пакетов много, а изменений требуется еще больше.

В какой-то момент стало понятно, что проблема не в конкретных инструментах или людях, а в самой модели хранения и обновления кода. Так мы и пришли к решению переехать в монорепозиторий на Nx.

Почему именно монорепозиторий

Важно учесть, что монорепозиторий — это не монолит. Мы распилили наш frontend-монолит на микрофронтенды, уйдя в отдельные репозитории для каждого проекта, но пришли опять к единственному репозиторию. Однако подходы отличаются: монорепозиторий — просто хранилище, по сути, изолированных пакетов и приложений с отдельными независимыми деплоями.

Монорепозиторий — это не серебряная пуля и подойдёт не всем. Он не делает систему автоматически проще и точно не подходит для любого масштаба. Но в нашем случае он позволял решить сразу несколько ключевых проблем:

  • Один MR на все зависимости без дополнительных бампов с ревью одного места;

  • Единый источник правды, где все приложения используют ui-kit из packages/ui-kit, а не из npm-реестра с рандомными версиями;

  • Shared-конфиги, и все приложения линтятся, тестируются и собираются по одним и тем же правилам;

  • Легче вносить изменения, когда не надо искать все места, где используется функционал, а IDE сама подсказывает возможные места ошибок.

Почему Nx

С тем, что монорепозиторий мы хотим, разобрались, но просто объединить код в один репозиторий не получится: пакеты и приложения надо разграничивать, а в идеале ещё и управлять кодом, сборкой и предоставлять удобные инструменты для генерации. Предлагаю кратко рассмотреть некоторые доступные альтернативы:

  • Пакетные менеджеры (workspaces npm, pnpm, yarn...) — простые инструменты для небольших монорепозиториев с базовыми кейсами отделения пакетов от приложений в рамках одного репозитория и управления их зависимостями отдельно;

  • Lerna — один из первых инструментов для работы с JS-монорепозиториями, используется чаще как более интересная версия workspaces для публикации пакетов и параллельных тасков. Поддерживается командой Nx;

  • Turborepo — более продвинутый инструмент для работы с задачами параллельно и с кэшем, имеет невысокую сложность настройки и хорошую производительность благодаря Rust. Является отличным выбором начиная от малых до крупных проектов, особенно актуален для Next.js, так как поддерживается инструмент командой Vercel;

  • Nx — наиболее популярный инструмент, выросший из экосистемы Angular, доступный на сегодняшний день практически любому frontend и не только стеку, с самым широким спектром предлагаемых возможностей: от распределенного выполнения и cloud-кэширования до генераторов и affected-команд с расширенным графом зависимостей.

По данным профильного опроса state of JavaScript 2025 большинству хватает встроенных возможностей пакетных менеджеров для управления зависимостями и построения монорпеозиториев.

Статистика использования монорепо инструментов за 2025
Статистика использования монорепо инструментов за 2025

Мы в свою очередь для своих целей выбрали Nx и вот почему:

  1. Поддержка module federation для наших микрофронтендов с конфигурацией host/remote и shared-пакетами, которая работает из коробки;

  2. Пайплайны выполнения задач при наличии зависимостей выручают, так как для сборки одного приложения автоматически затрагиваются все его зависимости;

  3. Отслеживание графа зависимостей пакетов и приложений для проектирования и устранения циклических импортов реально облегчает жизнь;

  4. Affected — запускает только те задачи, которые действительно нужны. То есть при сборке или тестах в CI будут выполняться только затронутые проекты, а не весь репозиторий;

  5. Кэширование задач, которое может работать локально и в облаке (в том числе и своем). Сборки пакетов или тесты без изменений при необходимости будут браться из кэша Nx менее чем за одну секунду;

  6. Генераторы, позволяющие развернуть новое уже настроенное приложение или библиотеку одной командой.

Цена переезда: долго и затратно

Важно сразу сказать: переезд в монорепозиторий — это не быстрый и не дешевый процесс. Это действительно долго и требует вложений времени команды. В нашем случае переезд занял около 12 недель.

Мы изначально понимали, что если делать это «в лоб», то пострадает продуктовая разработка. Поэтому переезд шёл параллельно с основной работой: мы закладывали время на погашение технического долга и постепенно переносили проекты в новую структуру. Это позволило не останавливать развитие продукта и не создавать критичных рисков для бизнеса.

Диаграмма Ганта по примерным срокам переезда в монорепозиторий
Диаграмма Ганта по примерным срокам переезда в монорепозиторий

Где монорепозиторий может не взлететь

Хочется сказать, что монорепозиторий и Nx — не универсальное решение. Если ваша команда и проекты небольшие (<5 проектов), то использование Nx может быть неоправданно сложным и дорогим. Или наоборот, если у вас гигантский продукт с разнообразным технологическим стеком и необходимостью использования большого количества установленных пакетов, то выгода от использования Nx снижается, а нагрузка при работе с подобным репозиторием будет высокой — как когнитивно, так и для локальных машин или CI.

Командные нюансы также имеют место при обсуждении: командам с большими MR и крупными релизами слишком часто придется актуализировать ветки и решать конфликты в монорепозитории, а при слишком частых деплоях будет затрагиваться общий код, увеличивая время ожидания пайплайнов и задевая все команды.

Ну и вишенкой на торте станет необходимость тонкой настройки инструмента, управляющего монорепозиторием. В случае с Nx это сотни строк конфига переплетающихся плагинов и задач. Настраивать так же тонко нужно и CI/CD, так как монорепозиторий, в котором на каждый коммит в MR будут проходить условные тесты, линтер и сборка на все затронутые проекты, будет потреблять достаточно много ресурсов. Если ваш GitLab с его раннерами или другая система не будут к этому готовы, то уже 2–3 одновременных MR с изменениями shared-пакета, который стриггерит еще с десяток приложений для тестирования, просто могут положить вашу инфраструктуру.

Что мы получили в итоге

После переезда стало заметно проще выносить общие пакеты и переиспользовать их между проектами без лишней головной боли. Архитектура стала более прозрачной, а изменения — более предсказуемыми. Мы не избавились от всех проблем разом, но существенно снизили операционные издержки на поддержку frontend-экосистемы.

Помимо сокращения времени работы над задачами по обновлению версий в нескольких репозиториях, мы получили кое-что более важное — выстроенный процесс, который оказался более оптимальным, чем был до этого. Теперь у команды разработки есть понимание, когда и куда можно выносить переиспользуемые вещи, мы смогли избавиться от циклических зависимостей (смело рекомендую eslint-правило @nx/enforce-module-boundaries) и оптимизировать архитектуру, разделив зоны ответственности.

Главный вывод здесь простой: монорепозиторий — это инвестиция. В начале действительно кажется, что вы просто создали себе проблемы на пустом месте, но если масштаб и связность проектов это оправдывают, в конце пути становится проще жить и развивать систему дальше.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Расскажите, какими инструментами для управления монорепозиториями вы пользуетесь на своих проектах! А если не пользуетесь никакими — расскажите, почему :)
14.29%Nx1
14.29%Turborepo1
14.29%Lerna1
14.29%npm workcpaces1
42.86%pnpm workcpaces3
14.29%yarn workcpaces1
0%Другое0
28.57%Не пользуюсь (мне бы с одним репозиторием справиться)2
Проголосовали 7 пользователей. Воздержались 2 пользователя.